--- url: /docs/kb/payout-descriptor.md description: >- How the sender's name appears on a recipient's bank statement varies by payment method and whether Named Account is enabled. --- ## Summary A payout descriptor is the sender name shown on the recipient's bank statement. By default it shows BlindPay's name (or "Nvio Pagos" for ACH Colombia and Transfers 3.0 Argentina). ACH, Domestic Wire, and International SWIFT support Named Accounts, which display the customer's own name instead. ## Descriptor by Payment Method | Payment Method | Scenario | Payout Descriptor | | --- | --- | --- | | ACH (U.S.) | No Named Account | BlindPay's name | | ACH (U.S.) | Named Account enabled | Customer's name | | Domestic Wire (U.S.) | No Named Account | BlindPay's name | | Domestic Wire (U.S.) | Named Account enabled | Customer's name | | International SWIFT | No Named Account | BlindPay's name | | International SWIFT | Named Account enabled | Customer's name | | RTP (U.S.) | All transfers | BlindPay's name | | PIX (Brazil) | All transfers | BlindPay's name | | SPEI (Mexico) | All transfers | BlindPay's name | | ACH Colombia | All transfers | Nvio Pagos | | Transfers 3.0 (Argentina) | All transfers | Nvio Pagos | ## Named Accounts ACH, Domestic Wire, and International SWIFT support Named Accounts, which display the customer's own name as the payout descriptor. To enable named accounts for a customer: 1. Contact BlindPay and specify which customer you want to enable named accounts for. 2. Allow up to **5 business days** for the request to be processed. Once enabled, recipients see the customer's name on their bank statements instead of BlindPay's name. **Note:** Named account requests must be submitted to BlindPay directly and take up to 5 business days to process. ## Related * [Cut-off Times](/docs/kb/cut-off-times) · [Customers](/docs/kb/kyc) --- --- url: /docs/kb/on-hold-transactions.md description: >- Transactions flagged as suspicious by BlindPay's monitoring system, held pending compliance review or a Request for Information. --- ## Summary On-hold transactions are payins or payouts that BlindPay's transaction monitoring system flags as suspicious to prevent fraud and money laundering. BlindPay's compliance team reviews each flagged transaction, and may send a Request for Information (RFI) asking for transaction details. If the RFI is not answered within 24 hours, the transaction may be refunded to the sender. ## How On-Hold Transactions Are Handled When a transaction is flagged, BlindPay's compliance team manually reviews it to determine whether it's a false positive. If the flag cannot be cleared internally, BlindPay sends you an email (or Slack message) with the transaction details and a [Request for Information (RFI)](/docs/kb/information-requests) asking for: * The relationship between the sender and the customer * The purpose of the transaction * The expected outcome of the transaction * Any other information that helps us understand the transaction Once you respond, the compliance team reviews the information and decides whether to release the transaction. **Note**: If the RFI is not answered within **24 hours**, the transaction may be refunded to the sender. ## Upcoming API Integration BlindPay is replacing the manual RFI process with a dedicated API endpoint for submitting transaction information programmatically: 1. You receive a [webhook](/docs/learn/webhooks) with the `on_hold` status. 2. You send a POST request to the new endpoint with the transaction information. 3. BlindPay collects and reviews it automatically. For programmatic handling of KYC/KYB RFIs, see the [RFI API documentation](/docs/kb/information-requests). ## Related * [Requests for Information](/docs/kb/information-requests) · [Webhooks](/docs/learn/webhooks) * [SWIFT Statuses](/docs/kb/swift-statuses) --- --- url: /docs/kb/nested-payments.md description: >- Nesting is moving money on behalf of a party BlindPay cannot see; this guide explains how to recognize it and stay compliant. --- ## Summary Nesting occurs when one BlindPay customer uses their account to move money for another party that BlindPay has not onboarded, screened, or approved. You may not use your BlindPay account, wallet, or virtual account to process, facilitate, or move payments on behalf of any party whose identity or transaction activity is not visible to BlindPay. If BlindPay cannot see who owns the funds, who is sending or receiving them, or what a transaction is for, the structure is nested and may be delayed, frozen, or terminated. ## The Core Rule > **You may not use your BlindPay account, wallet, or virtual account to process, facilitate, or move payments on behalf of any party whose identity or transaction activity is not visible to BlindPay.** That is the entire principle in one sentence. Everything else in this document exists to help you recognize what that looks like in practice for your specific business. ## What Is Nesting Nesting happens when one customer uses their BlindPay account to move money for another party that BlindPay has not onboarded, screened, or approved. The key word is **visibility**: if BlindPay cannot see who really owns the funds, who is really sending or receiving them, or what the transaction is really for, the structure is nested. ### Common Signs of Nesting * Funds in the account belong economically to someone other than the onboarded customer. * Payment documentation (invoices, contracts, payroll files) names a different entity than the account holder. * One account collects or distributes funds for multiple underlying businesses, users, or counterparties. * Sub-accounts or virtual accounts each represent a different third party's money, not the customer's own. * The customer is acting as an intermediary, aggregator, or pass-through layer without BlindPay's written approval. ### What Is NOT Nesting A transaction is generally fine when the onboarded customer is acting for itself, the funds are its own, and the economic purpose belongs to that customer. Legitimate revenue for services the customer actually provided, or ordinary expenses paid from its own account for its own operations, are not nesting. ## The Quick Test Before initiating any activity through BlindPay, run this simple check: 1. **Do the funds belong to your entity?** If no → likely nested. 2. **Does the documentation match your entity name?** If no → likely nested. 3. **Is the beneficiary visible to BlindPay?** If no → likely nested. 4. **Are there multiple underlying parties behind this single payment?** If yes → likely nested. 5. **All answers clear?** Proceed normally. ✅ Always provide a detailed and accurate description of the Nature of Business. Extensive details are required for a successful onboarding. ## Consequences of Non-Compliance If BlindPay determines that a customer is engaging in nesting or other restricted activity, BlindPay may take one or more of the following actions: delay, reject, freeze, or reverse the transaction where possible; request additional information or documentation; impose limits on account activity; suspend or terminate the BlindPay account. Where required, BlindPay may also report the activity to applicable law enforcement, regulatory authorities, banking partners, or other relevant counterparties consistent with its legal and compliance obligations. ## If Your Customer Was Rejected for Nesting A rejection for nesting does not mean the underlying business cannot work with BlindPay. If one of your customers was rejected because their structure would create a nested relationship, there is a compliant path forward: onboard that business as its own BlindPay instance. With their own instance, the business can then onboard all of its customers directly inside that instance. This gives BlindPay full visibility into every party involved, which avoids nested payments entirely. Key points about this option: * The new instance goes through the standard onboarding and compliance review as a direct BlindPay customer. * The new instance receives its own API keys, fully independent from yours. * There is no additional cost for setting up the new instance. * Once approved, the business onboards its own customers inside its instance, so every payment has a visible, screened counterparty. If this applies to one of your customers, reach out to our team and we will help set up the new instance. *This guidance uses terminology consistent with the BlindPay Terms and Conditions. Where any inconsistency exists between this document and the governing customer terms, any applicable separate written agreement, or written instructions provided by BlindPay, the governing terms and written approval framework shall control.* ## Related * [Terms of Service](/docs/kb/kyc) · [Customers](/docs/kb/kyc) --- --- url: /docs/kb/pobo-cobo.md description: >- How payment on behalf of and collection on behalf of work at BlindPay, what puts your customer's name on a Wire, and where the compliance line sits. --- ## Summary POBO (payment on behalf of) and COBO (collection on behalf of) let a platform send and receive international payments for its customers instead of forcing each one to open its own overseas bank account. At BlindPay both are built on two things: a virtual account issued to a specific onboarded customer, and the Named Account setting that puts that customer's name on the payment instead of BlindPay's. Neither works for a party BlindPay cannot see, which is the difference between POBO/COBO and [nested payments](/docs/kb/nested-payments). ## Definitions | Term | Direction | What it means | | --- | --- | --- | | **POBO** | Outbound | You pay a supplier for an obligation that belongs to your customer. The payee should be able to tell who the payment is really from. | | **COBO** | Inbound | You collect a payment that economically belongs to your customer. The payer should be able to send to an account that looks like your customer. | The terms come from corporate treasury, where a parent company pays and collects through one account on behalf of its subsidiaries. A platform on BlindPay is doing the same shape of thing for its customers rather than its own group companies, which changes the compliance requirements but not the mechanics. ## The Compliance Line POBO and COBO describe moving money for someone else, and BlindPay prohibits doing that for a party it cannot see. The two are compatible only because of who the "someone else" is. **Supported.** Your customer is onboarded into your instance, has passed KYC or KYB, and holds a virtual account in their own name. BlindPay can see who owns the funds, who is sending or receiving them, and what the payment is for. **Not supported.** Your customer uses their account to pay or collect for *their* customers, who BlindPay has never onboarded or screened. This is nesting, regardless of what the payment reference says. **Important**: A virtual account that holds a third party's money, where that third party is not itself an onboarded BlindPay customer, is a nested structure. If one of your customers needs to run payments for their own client base, the compliant path is their own BlindPay instance. See [Nested payments](/docs/kb/nested-payments). ## COBO: Collecting Wires Inbound international Wires require a virtual account. Creating a SWIFT payin without one returns `international_swift_payins_require_virtual_account`. The virtual account is issued per customer and carries its own beneficiary name, SWIFT BIC and account number, so the party paying your customer sends to details that identify your customer. See [Virtual accounts](/docs/virtual-accounts) to create one. When the Wire lands, the payin reports who sent it: | Field | What it carries | | --- | --- | | `sender_name` | Name of the party that sent the Wire | | `sender_bank_name` | Their bank | | `sender_account_number` | Their account | | `transaction_reference` | The reference they attached, useful for matching an invoice | These arrive on `GET /payins/{id}` and on the `payin.complete` webhook, so your customer's receivables can be reconciled without asking them who paid. ## POBO: Sending Wires ### Whose Name Appears By default the payout descriptor shows BlindPay's name. The **Named Account** setting replaces it with your customer's name, and it is available on ACH, Domestic Wire and International SWIFT only. It is enabled per customer on request and takes up to 5 business days to process. Full matrix in [Payout descriptor](/docs/kb/payout-descriptor). This is the part worth being precise about: your customer's name reaches the payee because the payment is issued against an account titled for that customer, not because BlindPay writes them into a separate ultimate-party field. See [What BlindPay Does Not Do](#what-blindpay-does-not-do) below. ### Minimum and Timing | Constraint | Value | | --- | --- | | Minimum SWIFT payout | 100 USD. Below this the payout is rejected with `swift_minimum_is_100_usd` | | Cut-off | 10:30 AM ET | | Estimated settlement | Up to 5 business days | ### Documents Every SWIFT payout is created `on_hold`. When the destination bank account's `recipient_relationship` is anything other than `first_party`, the payout also requires a compliance document proving the relationship between the sender and the customer, and it waits until that document is approved. Sending to your customer's own account (`recipient_relationship` set to `first_party`) requires no document. The beneficiary name must match the customer, otherwise the request fails with `beneficiary_name_must_match_customer_for_first_party`. Accepted document types are `invoice`, `purchase_order`, `delivery_slip`, `contract`, `customs_declaration`, `bill_of_lading` and `others`. Templates and the address formatting rules are in [SWIFT deliverability](/docs/kb/swift-deliverability); the review lifecycle and its timeouts are in [SWIFT statuses](/docs/kb/swift-statuses). Pass the invoice number as `transaction_document_id`. ### The Reference the Supplier Sees The `description` on the quote is what reaches the beneficiary bank. It maps to MT103 field 70 (`RmtInf` on pacs.008), which is the only non-bank field the beneficiary's bank surfaces as the payment reference. Put the invoice reference here so your customer's supplier can match the payment to their receivable. **Important**: the field accepts 128 characters but only the first **30** reach the beneficiary. Keep the reference short and put the invoice number first. `INV-2026-0418` survives; a full sentence does not. Two related fields exist and neither is beneficiary-facing. Payment purpose memo and instruction fields map to MT103 field 72, which correspondent banks largely drop, so a reference placed there will not appear on the supplier's statement. ## What the Payment Carries Back Each rail exposes a different reference, and BlindPay returns only the ones the rail actually provides. All of these arrive on `GET /payouts/{id}` and on the `payout.complete` webhook. | Rail | `provider_uetr` | `provider_imad` | `provider_reference` | `provider_clearing_system` | | --- | --- | --- | --- | --- | | International SWIFT | Yes | No | No | `SWIFT` | | Domestic Wire | Yes | Yes | Sometimes | `FED` or `CHIPS` | | ACH | No | No | Sometimes | `ACH` | * `provider_uetr` is the Unique End-to-end Transaction Reference. The network assigns it, so every bank in the chain refers to the same payment by the same identifier. It is populated once the Wire is confirmed and is null while the payment is in flight. * `provider_imad` is the Fed Input Message Accountability Data, and exists on domestic Fedwire payments only. * `provider_reference` is a bank-side reference captured from the account booking webhook. It appears on some ACH and domestic Wire payouts only. It is not the formal ACH network trace number and should not be handed to a bank as one. ## What BlindPay Does Not Do Treasury teams who have run POBO through a corporate bank sometimes expect the ISO 20022 ultimate-party fields. BlindPay does not populate `UltmtDbtr` or `UltmtCdtr`. A payment carries two non-bank parties: the account being debited and the beneficiary. Your customer's identity reaches the payee through the account title (Named Account) and the payment reference, not through a separate ultimate-debtor tag. Two consequences worth planning around: * Without Named Account enabled for that customer, the payee sees BlindPay as the sender and will reconcile the payment against BlindPay unless your reference tells them otherwise. * Some countries restrict or prohibit third-party payments outright, and a beneficiary bank can decline a payment that is not in the invoice party's name as a matter of policy. Check the destination before promising a corridor. See [Supported countries](/docs/kb/supported-countries). ## Related * [Virtual accounts](/docs/kb/virtual-accounts) · [Payout descriptor](/docs/kb/payout-descriptor) * [SWIFT deliverability](/docs/kb/swift-deliverability) · [SWIFT statuses](/docs/kb/swift-statuses) * [Nested payments](/docs/kb/nested-payments) · [Cut-off times](/docs/kb/cut-off-times) * Product overview: [POBO and COBO over SWIFT](https://blindpay.com/pobo-cobo-swift) --- --- url: /docs/kb/swift-deliverability.md description: >- Requirements for compliance documents and beneficiary address formatting that maximize the chance a SWIFT transfer is delivered. --- ## Summary SWIFT is the global payment network BlindPay uses to send and receive money internationally, processed exclusively through tier 1 banks. To maximize deliverability, every B2B SWIFT payment requires a compliance document proving the relationship between sender and customer, and beneficiary addresses must follow strict formatting rules with a 140-character limit. Documents that fail to establish the sender-customer relationship cause the payment to be rejected. ## Compliance Documentation Every B2B payment sent through SWIFT requires a transaction document showing the **relationship between the sender and the customer**. BlindPay accepts the following transaction documents: * [Invoice](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/invoice-template.pdf) * [Purchase Order](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/purchase-order-template.pdf) * [Delivery Slip](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/delivery-slip-template.pdf) * [Contract](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/contract-template.pdf) * [Customs Declaration](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/customs-declaration-template.pdf) * [Bill of Lading](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/bill-of-lading-template.pdf) * [Others](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/invoice-template.pdf) **Important**: If the document doesn't show the relationship between the sender and the customer, the payment will be rejected. To submit transaction documents via the API and track their review status, see the [SWIFT Statuses](/docs/kb/swift-statuses) guide. ## How to Add a SWIFT Account 1. In your instance, open **Customers** from the sidebar menu and select the customer you want to register the SWIFT account for. 2. Open **Bank Accounts**, then in **Add Bank Account** select the payment method **International Swift**. You can also add [bank accounts via the API](/docs/bank-accounts). 3. Enter the SWIFT account details: * Fill in all fields in **UPPERCASE only** * Use plain text only — no punctuation (no commas, periods, or special characters) * ✅ **Correct**: `JPMORGAN CHASE BANK NA SINGAPORE` * ❌ **Incorrect**: `J.P. Morgan Chase Bank, N.A., Singapore.` 4. If the SWIFT code has only 8 characters, append `XXX`: * `CHASSGSG` becomes `CHASSGSGXXX` * This routes communication directly to the bank's main branch. 5. Click **Create**. The SWIFT account is now linked to that customer. ## Beneficiary Address Requirements To ensure successful processing of SWIFT payments, addresses must follow strict formatting rules. All address elements combined must not exceed **140 characters**. ### Address Structure A complete SWIFT-compatible address consists of: 1. **Address Line 1** - Street name, building number, or primary physical location 2. **Address Line 2** - Additional location details (office, apartment, room, floor) 3. **City** - Name of the city or municipality 4. **State / Province Code** - ISO-style two-character state or region abbreviation 5. **Postal Code** - National postal identifier 6. **Country Code** - ISO 3166-1 alpha-2 country code ### Field Requirements | Field | Requirement | Example | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | | Address Line 1 | Max 70 characters combined with Addr2. Main street/building only. No P.O. Boxes unless the receiving bank explicitly allows them. | `123 Gran Via Building A` | | Address Line 2 | Max 70 characters combined with Addr1. Secondary details only (unit, suite, floor, block). | `Suite 405` | | City | Max 35 characters. Full city name, not abbreviations unless the official name is abbreviated. | `Madrid` | | State / Province Code | Exactly 2 characters. If the country has no states/provinces, repeat the 2-letter country code. | US → `CA`, ES → `ES` | | Postal Code | Max 16 characters. All-numeric or alphanumeric. No special symbols. HK → `999077`, UAE → `00000`. | US `94105`, UK `M4A1B3` | | Country Code | Exactly 2 letters, ISO 3166-1 alpha-2. | `ES` | ### Example: Correctly Formatted Address ``` Addr1: 123 Gran Via Bldg A Addr2: Suite 405 City: Madrid State: ES Postal Code: 28013 Country: ES ``` **Full combined output** (must be ≤ 140 chars): `123 Gran Via Bldg A Suite 405 Madrid ES 28013 ES` (59 characters) ### Example: Converting a Long Address **Original address** (too long for SWIFT): ``` Building 12, Zone C, Longhua Science & Technology Industrial Park, Minzhi Street, Longhua New District, Shenzhen City, Guangdong Province, China 518131 ``` **Converted SWIFT-compatible version**: ``` Addr1: Bldg 12 Zone C Longhua Sci-Tech Ind Park Addr2: Minzhi St City: Shenzhen State: CN (China does not use state codes → use country code) Postal Code: 518131 Country: CN ``` **Full combined output**: `Bldg 12 Zone C Longhua Sci-Tech Ind Park Minzhi St Shenzhen CN 518131 CN` (95 characters) ### Address Submission Checklist Before submitting an address, verify: * \[ ] Addr1 + Addr2 ≤ 70 characters * \[ ] City ≤ 35 characters * \[ ] State is exactly 2 letters * \[ ] Postal code ≤ 16 alphanumeric characters * \[ ] Country is exactly 2 letters * \[ ] Entire address ≤ 140 characters * \[ ] Address contains no unsupported symbols ## Related * [SWIFT Statuses](/docs/kb/swift-statuses) · [Bank Accounts](/docs/bank-accounts) * [Payouts](/docs/payouts) --- --- url: /docs/kb/swift-statuses.md description: >- How SWIFT payout compliance documents are tracked through on-hold, review, and approval via the tracking_documents field. --- ## Summary All international SWIFT payouts require compliance document submission before funds are processed. A payout stays on hold until documents are submitted and approved by BlindPay's compliance team. The `tracking_documents` field on payout responses and webhooks reports progress through `waiting_documents`, `compliance_reviewing`, and approval. Documents must be submitted within 30 days of payout creation and reviewed within 8 days of submission. ## Payout Flow for SWIFT When you create a [SWIFT payout](/docs/payouts), the flow is: 1. **Payout created** → Status is `on_hold` 2. **Waiting for documents** → `tracking_documents.status: waiting_documents` 3. **Documents submitted** → `tracking_documents.status: compliance_reviewing` 4. **Compliance approved** → Payout proceeds to `processing`, fiat is sent 5. **Compliance rejected** → Status returns to `waiting_documents`, submit new documents ## Submitting Documents To submit compliance documents and see the full request body, use the document submission endpoint documented on the [Payouts](/docs/payouts) page. ## Tracking Documents Field All payout responses and [webhooks](/docs/learn/webhooks) include `tracking_documents`: ```json { "id": "po_xxxxxxxxxxxxx", "status": "on_hold", "tracking_documents": { "step": "processing", "status": "waiting_documents", "reviewed_by": null, "completed_at": null } } ``` ### Status Values | Status | Description | | ---------------------- | ------------------------------------- | | `waiting_documents` | Awaiting document submission | | `compliance_reviewing` | Documents submitted, under review | | `null` | Documents approved, payout processing | ### Step Values | Step | Description | | ------------ | --------------------------------- | | `processing` | Document verification in progress | | `completed` | Document verification complete | ## Webhook Examples ### Documents Submitted ```json { "id": "po_xxxxxxxxxxxxx", "status": "on_hold", "tracking_documents": { "step": "processing", "status": "compliance_reviewing", "reviewed_by": null, "completed_at": null } } ``` ### Compliance Approved ```json { "id": "po_xxxxxxxxxxxxx", "status": "processing", "tracking_documents": { "step": "completed", "status": null, "reviewed_by": "compliance@blindpay.com", "completed_at": "2026-01-27T14:30:00.000Z" } } ``` ### Compliance Rejected ```json { "id": "po_xxxxxxxxxxxxx", "status": "on_hold", "tracking_documents": { "step": "processing", "status": "waiting_documents", "reviewed_by": "compliance@blindpay.com", "completed_at": null } } ``` ## Timeouts | Event | Timeout | | ------------------- | ------------------------------- | | Document submission | 30 days from payout creation | | Compliance review | 8 days from document submission | ## Accepted Documents * Invoice * Contract * Purchase Order * Delivery Slip * Customs Declaration * Bill of Lading * Others Document templates are available in the [SWIFT Deliverability](/docs/kb/swift-deliverability) guide. **Important**: Documents must show the relationship between the sender and the customer. If the document doesn't clearly establish this relationship, the payment will be rejected. ## First Party Payouts If you are sending funds to your own bank account (the sender and the customer are the same person or entity), set the bank account's `recipient_relationship` to `first_party`. No compliance document is required, so `transaction_document_type`, `transaction_document_id` and `transaction_document_file` do not apply. The beneficiary name must match the customer. This is common when consolidating funds across your own accounts internationally. ## Related * [SWIFT Deliverability](/docs/kb/swift-deliverability) · [Payouts](/docs/payouts) * [Webhooks](/docs/learn/webhooks) · [On-Hold Transactions](/docs/kb/on-hold-transactions) --- --- url: /docs/kb/supported-countries.md description: >- Every country BlindPay supports, by tier: standard, high-risk (Enhanced KYC required), and prohibited. --- BlindPay supports customers and transactions across most countries. Which verification level a customer needs, and whether a country is supported at all, depends on where they are from. Country checks apply to a customer's `country`, `id_doc_country`, and (for businesses) each owner's `id_doc_country`, as well as bank account fields such as the SWIFT beneficiary/bank/intermediary country. There are three tiers: | Tier | What it means | | --- | --- | | Standard | KYC Standard (individuals) or KYB Standard (businesses) is available. Automated review for eligible individuals. | | High-risk | Individuals must use KYC Enhanced. Manual review, up to 1 business day. Driver's license is not accepted as an ID document. | | Prohibited | Not supported. Customer and bank account creation is blocked. | High-risk status is a soft gate: it raises the KYC requirement, it does not block the country. Prohibited countries are a hard block. ## High-risk countries Individuals from high-risk countries cannot use `kyc_type: standard`; creating one with `standard` is rejected. They must use `kyc_type: enhanced` instead, which requires the additional Enhanced KYC fields (source of funds, purpose of transactions) and goes through manual review. * Manual review typically takes up to 1 business day. * Driver's license (`DRIVERS`) is not accepted as an `id_doc_type` for a high-risk `id_doc_country`; use a passport or national ID card instead. * Businesses are not gated by this list directly, but a business owner's `id_doc_country` is still checked against the prohibited-country list. ## Prohibited countries Countries under sanctions or outside BlindPay's risk appetite are not supported at all. Creating a customer, adding a bank account, or setting a SWIFT beneficiary/bank/intermediary country that falls in this tier returns an error and the request is blocked. There is no override or manual exception path for a prohibited country. ## Country list The table below lists every country and its current tier. | Country | Code | Tier | | --- | --- | --- | | Åland Islands | AX | Standard | | Albania | AL | Standard | | Andorra | AD | Standard | | Antarctica | AQ | Standard | | Antigua and Barbuda | AG | Standard | | Argentina | AR | Standard | | Armenia | AM | Standard | | Aruba | AW | Standard | | Australia | AU | Standard | | Austria | AT | Standard | | Azerbaijan | AZ | Standard | | Bahrain | BH | Standard | | Bangladesh | BD | Standard | | Belgium | BE | Standard | | Belize | BZ | Standard | | Bermuda | BM | Standard | | Bhutan | BT | Standard | | Bosnia and Herzegovina | BA | Standard | | Bouvet Island | BV | Standard | | Brazil | BR | Standard | | Brunei Darussalam | BN | Standard | | Bulgaria | BG | Standard | | Cabo Verde | CV | Standard | | Canada | CA | Standard | | Cayman Islands | KY | Standard | | Chile | CL | Standard | | China | CN | Standard | | Costa Rica | CR | Standard | | Croatia | HR | Standard | | Curaçao | CW | Standard | | Cyprus | CY | Standard | | Czechia | CZ | Standard | | Denmark | DK | Standard | | Dominican Republic | DO | Standard | | Ecuador | EC | Standard | | Estonia | EE | Standard | | Falkland Islands \[Malvinas] | FK | Standard | | Faroe Islands | FO | Standard | | Finland | FI | Standard | | France | FR | Standard | | French Guiana | GF | Standard | | French Polynesia | PF | Standard | | French Southern Territories | TF | Standard | | Gambia | GM | Standard | | Georgia | GE | Standard | | Germany | DE | Standard | | Gibraltar | GI | Standard | | Greece | GR | Standard | | Greenland | GL | Standard | | Grenada | GD | Standard | | Guadeloupe | GP | Standard | | Guernsey | GG | Standard | | Guyana | GY | Standard | | Heard Island and McDonald Islands | HM | Standard | | Holy See | VA | Standard | | Honduras | HN | Standard | | Hong Kong | HK | Standard | | Hungary | HU | Standard | | Iceland | IS | Standard | | India | IN | Standard | | Indonesia | ID | Standard | | Ireland | IE | Standard | | Isle of Man | IM | Standard | | Israel | IL | Standard | | Italy | IT | Standard | | Japan | JP | Standard | | Jersey | JE | Standard | | Jordan | JO | Standard | | Kazakhstan | KZ | Standard | | Korea (the Republic of) | KR | Standard | | Kuwait | KW | Standard | | Latvia | LV | Standard | | Lesotho | LS | Standard | | Liechtenstein | LI | Standard | | Lithuania | LT | Standard | | Luxembourg | LU | Standard | | Macao | MO | Standard | | Malawi | MW | Standard | | Malaysia | MY | Standard | | Maldives | MV | Standard | | Malta | MT | Standard | | Marshall Islands | MH | Standard | | Martinique | MQ | Standard | | Mauritius | MU | Standard | | Mayotte | YT | Standard | | Mexico | MX | Standard | | Micronesia (Federated States of) | FM | Standard | | Mongolia | MN | Standard | | Montenegro | ME | Standard | | Montserrat | MS | Standard | | Morocco | MA | Standard | | Nauru | NR | Standard | | Netherlands | NL | Standard | | New Zealand | NZ | Standard | | Niue | NU | Standard | | Norway | NO | Standard | | Oman | OM | Standard | | Palau | PW | Standard | | Papua New Guinea | PG | Standard | | Paraguay | PY | Standard | | Peru | PE | Standard | | Philippines | PH | Standard | | Pitcairn | PN | Standard | | Poland | PL | Standard | | Portugal | PT | Standard | | Puerto Rico | PR | Standard | | Republic of North Macedonia | MK | Standard | | Réunion | RE | Standard | | Romania | RO | Standard | | Saint Helena, Ascension and Tristan da Cunha | SH | Standard | | Saint Kitts and Nevis | KN | Standard | | Saint Lucia | LC | Standard | | Saint Vincent and the Grenadines | VC | Standard | | San Marino | SM | Standard | | Sao Tome and Principe | ST | Standard | | Saudi Arabia | SA | Standard | | Serbia | RS | Standard | | Seychelles | SC | Standard | | Singapore | SG | Standard | | Slovakia | SK | Standard | | Slovenia | SI | Standard | | South Georgia and the South Sandwich Islands | GS | Standard | | Spain | ES | Standard | | Sri Lanka | LK | Standard | | Svalbard and Jan Mayen | SJ | Standard | | Sweden | SE | Standard | | Switzerland | CH | Standard | | Taiwan | TW | Standard | | Thailand | TH | Standard | | Timor-Leste | TL | Standard | | Turkey | TR | Standard | | Turks and Caicos Islands | TC | Standard | | Tuvalu | TV | Standard | | United Arab Emirates | AE | Standard | | United Kingdom of Great Britain and Northern Ireland | GB | Standard | | United States Minor Outlying Islands | UM | Standard | | United States of America | US | Standard | | Uruguay | UY | Standard | | Uzbekistan | UZ | Standard | | Wallis and Futuna | WF | Standard | | Algeria | DZ | High-risk | | American Samoa | AS | High-risk | | Angola | AO | High-risk | | Anguilla | AI | High-risk | | Bahamas | BS | High-risk | | Barbados | BB | High-risk | | Benin | BJ | High-risk | | Bolivia (Plurinational State of) | BO | High-risk | | Bonaire, Sint Eustatius and Saba | BQ | High-risk | | Botswana | BW | High-risk | | British Indian Ocean Territory | IO | High-risk | | Burkina Faso | BF | High-risk | | Burundi | BI | High-risk | | Cambodia | KH | High-risk | | Cameroon | CM | High-risk | | Chad | TD | High-risk | | Christmas Island | CX | High-risk | | Cocos (Keeling) Islands | CC | High-risk | | Colombia | CO | High-risk | | Comoros | KM | High-risk | | Cook Islands | CK | High-risk | | Côte d'Ivoire | CI | High-risk | | Djibouti | DJ | High-risk | | Dominica | DM | High-risk | | Egypt | EG | High-risk | | El Salvador | SV | High-risk | | Equatorial Guinea | GQ | High-risk | | Eritrea | ER | High-risk | | Eswatini | SZ | High-risk | | Ethiopia | ET | High-risk | | Fiji | FJ | High-risk | | Gabon | GA | High-risk | | Ghana | GH | High-risk | | Guam | GU | High-risk | | Guatemala | GT | High-risk | | Guinea-Bissau | GW | High-risk | | Haiti | HT | High-risk | | Jamaica | JM | High-risk | | Kenya | KE | High-risk | | Kiribati | KI | High-risk | | Kyrgyzstan | KG | High-risk | | Lao People's Democratic Republic | LA | High-risk | | Lebanon | LB | High-risk | | Liberia | LR | High-risk | | Madagascar | MG | High-risk | | Mauritania | MR | High-risk | | Moldova (the Republic of) | MD | High-risk | | Monaco | MC | High-risk | | Mozambique | MZ | High-risk | | Namibia | NA | High-risk | | Nepal | NP | High-risk | | New Caledonia | NC | High-risk | | Nicaragua | NI | High-risk | | Niger | NE | High-risk | | Norfolk Island | NF | High-risk | | Northern Mariana Islands | MP | High-risk | | Pakistan | PK | High-risk | | Panama | PA | High-risk | | Rwanda | RW | High-risk | | Saint Barthélemy | BL | High-risk | | Saint Martin (French part) | MF | High-risk | | Saint Pierre and Miquelon | PM | High-risk | | Samoa | WS | High-risk | | Senegal | SN | High-risk | | Sierra Leone | SL | High-risk | | Sint Maarten (Dutch part) | SX | High-risk | | Solomon Islands | SB | High-risk | | South Africa | ZA | High-risk | | Suriname | SR | High-risk | | Syrian Arab Republic | SY | High-risk | | Tajikistan | TJ | High-risk | | Tanzania, United Republic of | TZ | High-risk | | Togo | TG | High-risk | | Tokelau | TK | High-risk | | Tonga | TO | High-risk | | Trinidad and Tobago | TT | High-risk | | Tunisia | TN | High-risk | | Turkmenistan | TM | High-risk | | Uganda | UG | High-risk | | Vanuatu | VU | High-risk | | Viet Nam | VN | High-risk | | Virgin Islands (British) | VG | High-risk | | Virgin Islands (U.S.) | VI | High-risk | | Western Sahara | EH | High-risk | | Zambia | ZM | High-risk | | Zimbabwe | ZW | High-risk | | Afghanistan | AF | Prohibited | | Belarus | BY | Prohibited | | Central African Republic | CF | Prohibited | | Congo | CG | Prohibited | | Congo (the Democratic Republic of the) | CD | Prohibited | | Cuba | CU | Prohibited | | Guinea | GN | Prohibited | | Iran (Islamic Republic of) | IR | Prohibited | | Iraq | IQ | Prohibited | | Korea (the Democratic People's Republic of) | KP | Prohibited | | Libya | LY | Prohibited | | Mali | ML | Prohibited | | Myanmar | MM | Prohibited | | Nigeria | NG | Prohibited | | Palestine, State of | PS | Prohibited | | Qatar | QA | Prohibited | | Russian Federation | RU | Prohibited | | Somalia | SO | Prohibited | | South Sudan | SS | Prohibited | | Sudan | SD | Prohibited | | Ukraine | UA | Prohibited | | Venezuela (Bolivarian Republic of) | VE | Prohibited | | Yemen | YE | Prohibited | Ukraine's prohibited status reflects the sanctioned regions (Crimea, Donetsk, Luhansk, Zaporizhzhia, and Kherson). Country tiers are reviewed periodically as sanctions and risk assessments change, so a country's tier can move. Contact support@blindpay.com if you need to confirm a country's current tier before launching there. ## What to build against Because these tiers can change, avoid hardcoding the list into your own product logic. Instead: * Let the customer creation call be the source of truth: submit the customer with the country and KYC type you intend, and handle the error response if the country requires a different tier. * For individuals from high-risk countries, collect the Enhanced KYC fields up front. Enhanced KYC always goes through manual review; there is no fast-path back to standard's automated speed. ## Related * [Payment methods](/docs/kb/payment-methods): which bank rails are available in each country * [KYC requirements](/docs/kb/kyc): verification levels, required fields, and statuses referenced by country tier * [Instances](/docs/learn/instances): sandbox vs. production behavior for customer creation and KYC --- --- url: /docs/kb/supported-chains.md description: >- Reference for the blockchains, stablecoins, and per-feature chain support across BlindPay payins, payouts, wallets, and transfers. --- BlindPay settles stablecoins on Ethereum, Polygon, Base, Arbitrum (EVM), Stellar, Solana, and Tron. Production instances run on mainnets with USDC or USDT; development instances run on the matching testnets with USDB, BlindPay's test stablecoin. There is no runtime endpoint that lists supported chains or tokens. This page is the reference: the enums below are fixed by the API and are the source of truth for which `network` and `token` values you can send. ## Chains | Chain | Mainnet chain ID | Testnet (development) | Testnet chain ID | Mainnet stablecoins | | --- | --- | --- | --- | --- | | Ethereum | 1 | Ethereum Sepolia | 11155111 | USDC, USDT | | Polygon | 137 | Polygon Amoy | 80002 | USDC, USDT | | Base | 8453 | Base Sepolia | 84532 | USDC | | Arbitrum | 42161 | Arbitrum Sepolia | 421614 | USDC | | Stellar | n/a | Stellar Testnet | n/a | USDC | | Solana | n/a | Solana Devnet | n/a | USDC, USDT | | Tron | n/a | none | n/a | USDT | Stellar, Solana, and Tron do not have a real numeric chain ID; the API accepts the network name directly (`stellar`, `solana`, `tron`), not a chain ID. Tron has no testnet, so it cannot be exercised on a development instance. USDB, BlindPay's test stablecoin, is available only on development instances and only on the six testnets above (`sepolia`, `base_sepolia`, `arbitrum_sepolia`, `polygon_amoy`, `stellar_testnet`, `solana_devnet`). It has no mainnet deployment on any chain. Mint it from the dashboard (EVM) or via the mint endpoints (Stellar, Solana). See [Mint USDB](/docs/mint-usdb). A production instance can only use `USDC` or `USDT` on a mainnet network. A development instance can only use `USDB` on a testnet network. Sending a mismatched combination (for example `USDC` on `sepolia`, or `USDB` on `polygon`) is rejected. The one exception is a cross-chain USDC transfer (see below): on a development instance it uses real testnet USDC instead of USDB, since [Circle CCTP v2](/docs/transfer-quotes#cross-chain-usdc-transfers-circle-cctp-v2) can only move native USDC. ## Support by feature "Payins" are the stablecoin delivery side of a bank deposit (on-ramp); "payouts" are the stablecoin source side of a bank transfer out (off-ramp). See [Bank transfer in](/docs/payins) and [Pay out to bank](/docs/payouts) for the fiat-side flow, or [Payins](/docs/payins) and [Payouts](/docs/payouts) for the full crypto mechanics. | Feature | Chains | Stablecoins | Notes | | --- | --- | --- | --- | | Payins (on-ramp delivery) | Ethereum, Polygon, Base, Arbitrum, Stellar, Solana, Tron | USDC, USDT (network-dependent; USDC not on Tron) | | | Payouts (off-ramp source) | Ethereum, Polygon, Base, Arbitrum, Stellar, Solana, Tron | USDC, USDT (network-dependent; USDC not on Tron; USDT not on Base/Arbitrum/Stellar) | | | Managed wallets (beta) | Ethereum, Polygon, Base, Arbitrum, Solana | USDC, USDT (USDB on testnet) | | | External blockchain wallets | Ethereum, Polygon, Base, Arbitrum, Stellar, Solana, Tron | USDC, USDT (network-dependent; USDC not on Tron; USDT not on Base/Arbitrum/Stellar) | Store an address you already control; see [Managed wallet](/docs/store) | | Transfers (beta) | Same network, except USDC which can also cross Ethereum, Polygon, Base, Arbitrum via Circle CCTP v2 | USDC, USDT (USDT restricted to Polygon) | No token conversion | Managed wallets and transfers are beta features. Production access for beta features is granted on request. Not every token is deployed on every chain. In particular: * USDC is not deployed on Tron. * USDT is only deployed on Polygon, Ethereum, Tron, and Solana, not on Base, Arbitrum, or Stellar. * USDB only exists on the six development testnets, never on a mainnet. The quote-creation endpoints enforce these combinations and return a descriptive error (for example, a token not supported on the requested chain) if you request an unsupported pairing. ## Related * [Bank transfer in](/docs/payins): payin quotes and payment methods on the fiat side * [Pay out to bank](/docs/payouts): payout quotes and bank account types on the fiat side * [Payins](/docs/payins): payin delivery networks and corridors * [Payouts](/docs/payouts): payout authorization per network, including Stellar and Solana * [Managed wallet](/docs/store): wallet chain support and balance operations --- --- url: /docs/kb/payment-methods.md description: >- Every bank transfer rail BlindPay supports, by country and currency: ACH, wire, RTP, SWIFT, Pix, SPEI, PSE, Transfers, and SEPA. --- BlindPay supports bank transfers over local rails in the US, Brazil, Mexico, Colombia, Argentina, and Europe, plus international SWIFT globally. Each payment method moves money in one or both directions: **receive** (a payin, money coming into BlindPay) and **send** (a payout, money going out to a bank account). | Payment method | Country/Region | Currency | Direction | | --- | --- | --- | --- | | International SWIFT | 🌎 Global | USD | Receive + send | | ACH | 🇺🇸 United States | USD | Receive + send | | Domestic Wire | 🇺🇸 United States | USD | Receive + send | | RTP | 🇺🇸 United States | USD | Receive + send | | Pix | 🇧🇷 Brazil | BRL | Receive + send | | SPEI | 🇲🇽 Mexico | MXN | Receive + send | | PSE | 🇨🇴 Colombia | COP | Receive | | ACH Colombia | 🇨🇴 Colombia | COP | Send | | Transfers 3.0 | 🇦🇷 Argentina | ARS | Receive + send | | SEPA | 🇪🇺 Europe (SEPA zone) | EUR | Send | On the receive side, US payments arrive either into a customer's own [virtual account](/docs/virtual-accounts) or into BlindPay's bank details with a `memo_code`. Alternatively, an ACH payin can pull the funds directly from a bank account the customer connected through [Plaid](/docs/bank-accounts#connect-with-plaid), skipping the manual transfer entirely; see [Payins](/docs/payins#pull-funding-from-a-plaid-connected-account). ## SEPA destinations SEPA payouts in EUR are available to bank accounts in: Albania, Andorra, Austria, Belgium, Bulgaria, Croatia, Cyprus, Czechia, Denmark, Estonia, Finland, France, Germany, Greece, Holy See, Hungary, Iceland, Ireland, Italy, Latvia, Liechtenstein, Lithuania, Luxembourg, Malta, Moldova, Monaco, Montenegro, Netherlands, North Macedonia, Norway, Poland, Portugal, Romania, San Marino, Slovakia, Slovenia, Spain, Sweden, Switzerland, and the United Kingdom. SEPA payouts to Austria, Estonia, Finland, France, Lithuania, Norway, and Portugal are currently limited to individual beneficiaries (`account_class: individual`). ## Settlement and cut-offs How fast each rail settles, and the daily cut-offs for ACH, wire, and SWIFT, are covered in [Cut-off times](/docs/kb/cut-off-times). As a rule of thumb: Pix, SPEI, and Transfers settle in minutes; RTP is instant; ACH, wire, ACH Colombia, and SEPA take about 1-2 business days; SWIFT can take up to 5 business days. ## Related * [Supported countries](/docs/kb/supported-countries): which countries customers can onboard from * [Bank transfer in](/docs/payins): payin quotes and what to show the payer * [Pay out to bank](/docs/payouts): adding bank accounts and sending payouts * [Cut-off times](/docs/kb/cut-off-times): settlement windows and processing cut-offs per rail --- --- url: /docs/kb/smart-contracts.md description: >- USDB is BlindPay's test stablecoin, freely mintable on testnets, with deployed contract addresses across supported networks. --- ## Summary BlindPay provides USDB, a test stablecoin you can mint freely on testnets to develop and test your integration. The contract is a standard ERC-20 with an open `mintUSDB` function, deployed on Sepolia, Arbitrum Sepolia, Base Sepolia, and Polygon Amoy. ## USDB Test Stablecoin Contract code: ```solidity [BankAccounts.sol] // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract USDB is ERC20 { constructor() ERC20("USDB", "USDB") { _mint(msg.sender, 5000 * 10 ** 18); } function mintUSDB(uint256 amount) external { _mint(msg.sender, amount); } } ``` Deployed addresses: | Network | Address | | ---------------- | ------------------------------------------ | | Sepolia | 0x8Cb65c1334b348E8d486AC935a784967AAEbB6e3 | | Arbitrum Sepolia | 0x4D423D2cfB373862B8E12843B6175752dc75f795 | | Base Sepolia | 0x4D423D2cfB373862B8E12843B6175752dc75f795 | | Polygon Amoy | 0x587C3D85C9272484A6e40a8300290F55a4D5a589 | --- --- url: /docs/kb/cut-off-times.md description: >- ACH, wire, and SWIFT cut-offs and settlement, instant rails, quote expiry windows, onboarding SLAs, and refund timing. --- Requests submitted after a payment method's daily cut-off are processed the next business day. Business days exclude weekends, US federal holidays, and applicable international banking holidays. The cut-off and settlement windows below describe the underlying ACH, wire, and SWIFT processing networks in general. They are not the same as BlindPay's end-to-end ETAs for a specific payin or payout, which also include BlindPay's own processing time. For payin arrival windows see [Bank transfer in](/docs/payins); for payout ETAs by bank account type see [Pay out to bank](/docs/payouts). ## ACH | Method | Cut-off (ET) | Estimated settlement | | --- | --- | --- | | ACH | 9:00 PM | 1-3 business days | | Same-Day ACH | 3:00 PM | Same business day | For US payments, customers with an enabled [virtual account](/docs/virtual-accounts) see their own account details displayed to the payer. Customers without a virtual account get a unique `memo_code` plus BlindPay's bank account details for the transaction. The memo code is ignored once a virtual account is approved. ## Wire and SWIFT | Type | Cut-off (ET) | Estimated settlement | | --- | --- | --- | | Domestic wire | 3:00 PM | Same business day | | International SWIFT | 10:30 AM | Up to 5 business days | SWIFT payouts carry extra country-conditional required fields (a NAICS `business_industry` code for business accounts, a local `tax_id` for the beneficiary's country, and `phone_number` for certain countries). See [Pay out to bank](/docs/payouts) for the full field list per country. ## Instant methods These rails settle in minutes rather than business days, both for payins (money coming in) and payouts (money going out): | Method | Currency | Country | Payin arrival | Payout `type` value | | --- | --- | --- | --- | --- | | Pix | BRL | Brazil | Up to 5 minutes | `pix` | | SPEI (CLABE) | MXN | Mexico | Up to 10 minutes | `spei_bitso` | | Transfers (CBU) | ARS | Argentina | Up to 10 minutes | `transfers_bitso` | | PSE | COP | Colombia | Up to 10 minutes | (payin only; payout equivalent is `ach_cop_bitso`) | On the payout side, `ach_cop_bitso` (Colombia) settles in around 1 business day, not instantly. High transaction volumes may affect estimated delivery times on any rail. In development, every payin auto-completes about 30 seconds after initiation, regardless of payment method. ## Currency minimums Payin quotes enforce a minimum and maximum `request_amount` (minor units) per currency. Most currencies share the same bounds, but COP's minimum is far higher in raw minor units, reflecting its much smaller nominal value per unit: | Currency | Minimum | Maximum | | --- | --- | --- | | USD | $10.00 | $100,000 | | BRL | R$10.00 | R$100,000 | | ARS | $10.00 | $100,000 | | MXN | $10.00 | $100,000 | | EUR | €10.00 | €100,000 | | **COP** | **$2,000.00** | $100,000 | Thresholds are enforced at quote-creation time and can change; treat the min/max as "varies by currency, the quote enforces it" rather than hardcoding these numbers in your integration. Payin methods without an approved virtual account (`ach`, `wire`) are additionally capped at $500,000 per transaction. ## Quote expiry windows Every quote has a limited lifetime. Read `expires_at` from the response rather than assuming a fixed TTL, since some rails can return a shorter window. | Quote type | Default expiry | Notes | | --- | --- | --- | | Payin quote | 5 minutes | OTC (BRL-only) payin quotes expire in **10 seconds** instead | | Payout quote | 5 minutes | May be **shorter** for SEPA payout quotes, since the underlying rail's own deadline can be tighter than 5 minutes | | Transfer quote | 5 minutes | Fixed; transfers execute immediately after creation | `expires_at` is returned in **epoch milliseconds**, not seconds. Divide by 1000 only if your date library expects seconds. ## Onboarding SLAs | Service | Timeline | | --- | --- | | New instance creation | Up to 3 business days | | KYC Standard | About 60 seconds (automated) | | KYC Enhanced | 3 hours to 1 business day (manual review) | | KYB Standard | 3 hours to 1 business day (manual review) | | Virtual account: compliance review | Part of overall review | | Virtual account: bank review | Varies by banking partner and account type; typically several business days | | Limit increase review | Reviewed on submission of supporting documents | For a limit increase review, the accepted supporting documents are: * **Individuals:** bank statement, tax return, or proof of income * **Businesses:** bank statement, financial statements, or tax return KYC Standard customers are typically auto-approved or auto-rejected after about 60 seconds. If the compliance team needs to review manually, the customer stays in `verifying` until they decide. KYC Enhanced and KYB Standard always require manual review. Virtual accounts go through a two-stage review: compliance review (`pending_review`) followed by bank review (`verifying`), before reaching `approved` or `rejected`. In development, virtual accounts auto-approve. If the bank requests additional documents during its review, the SLA clock restarts from the date the new documents are submitted. ## Compliance holds Certain transactions or onboarding steps may be held for compliance review. Common triggers: * First-time withdrawals or unusual activity patterns * Large transaction amounts relative to a customer's history * Sanctions or watchlist screening matches * Customers from high-risk countries (always routed to Enhanced KYC, manual review) * Open [requests for information](/docs/kb/kyc) on a customer (status `compliance_request`, which cannot stack, only one RFI is open at a time) While a hold is open, the related customer, payin, payout, or virtual account stays in a pending or verifying state until compliance clears it. A payout can also land `on_hold` for manual review after crypto has already been collected from the sender; this applies to all USD ACH/Wire/RTP/SWIFT payouts, not only risk-flagged ones, and can take up to 30 days to resolve. ## Failed or refunded transactions A payin or payout may fail or be refunded if: * Beneficiary or bank account details are incorrect * The receiving bank rejects the payment * The receiving account is closed or restricted * Compliance requirements are not met Refund timing depends on which side of the rail the funds are on: * **Stablecoin refunds** (the blockchain wallet side) process immediately, since BlindPay is non-custodial and funds simply return to the originating wallet. * **Fiat refunds** are credited once the funds are returned from the banking network, which depends on that bank's own processing time. Fees may apply to fiat refunds. In development, force these outcomes on a payin or payout by setting `request_amount` to `66600` ($666.00, forces `failed`) or `77700` ($777.00, forces `refunded`). ## Related * [Bank transfer in](/docs/payins): payin payment methods and arrival windows * [Pay out to bank](/docs/payouts): payout bank account types and ETAs * [Payin quotes](/docs/payin-quotes): creating and consuming a payin quote before its expiry * [Virtual accounts](/docs/virtual-accounts): virtual account review stages and statuses * [KYC requirements](/docs/kb/kyc): verification levels, required fields, and limits --- --- url: /docs/kb/kyc-basics.md description: >- Required KYC verification levels, document-quality standards, and submission guidelines for BlindPay customers. --- ## Summary Every customer on BlindPay must complete a Know Your Customer (KYC) verification before sending or receiving funds. Individuals are verified at one of two levels: KYC Standard (automated) or KYC Enhanced (manual review for high-risk countries). A complete, high-quality document submission avoids delays and follow-up requests. ## Verification levels BlindPay offers two KYC levels for individuals: * **KYC Standard** is the default verification. It's automated and typically completes in about 60 seconds. * **KYC Enhanced** is required for individuals from high-risk countries. It includes everything from KYC Standard plus additional documentation, and all submissions are manually reviewed by the compliance team. For the full list of required fields, see [Customers](/docs/kb/kyc#required-fields). ## Document quality A complete, high-quality submission avoids delays and follow-up requests. ![Identity document](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/blindpay-kyc-best-practices.jpeg) ### Photo tips * Photograph the **original physical document** — not a screen or printed copy. * Make sure all text is **clear and readable**, with no glare or reflections. * Capture the **entire document** in the frame — don't crop any edges. * Use **good lighting** and keep the image **in focus**. * If applicable, send **both the front and back** of the document. ### Avoid submitting * Photos taken of a screen, monitor, or another device * Screenshots, or pictures of printed or photocopied documents * Blurry, dark, cropped, or unclear images **Note:** Screenshots are not accepted unless explicitly approved by our team. ### Submission format * Upload a **clear, color scan or photo** of the document * Accepted formats: **PDF, JPEG, or PNG** * Maximum file size: **3 MB** * The **entire document** must be visible — no cropped or partial images ## Related --- --- url: /docs/kb/kyc.md description: >- Verification levels, required fields, statuses, limits, document quality, terms of service, and RFIs for BlindPay customers. --- Every payment flows through a customer that has completed KYC. BlindPay offers two verification levels for individuals and one level for businesses. A customer is an individual or business entity you register before it can send or receive funds; you can attach multiple bank accounts, blockchain wallets, and [managed wallets](/docs/store) to a customer. For compliance and regulatory requirements, every customer on your platform must be registered as a customer in BlindPay. This is mandatory for transaction tracking and reporting. If any of your customers operate as money transmitters (entities that transfer funds on behalf of others), they must also register their end customers. This multi-level registration keeps the payment chain transparent end to end. ## Verification levels | Level | Who it applies to | Review type | Typical time | | --- | --- | --- | --- | | KYC Standard | Individuals (standard-risk countries) | Automated | ~60 seconds | | KYC Enhanced | Individuals (high-risk countries) | Manual | 3 hours to 1 business day | | KYB Standard | Businesses | Manual | 3 hours to 1 business day | Set `type` (`individual` or `business`) and `kyc_type` (`standard` or `enhanced`) when you create the customer. Individuals from high-risk countries must use `enhanced`; `standard` is rejected for them. Businesses only have one tier, KYB Standard; there is no enhanced KYB. See the [API reference](https://api.blindpay.com/reference#tag/customers) for the complete field list. ## Required fields Fields marked optional below are not required to create the customer, but missing them can slow down manual review. ### KYC Standard (individual) | Field | Required | | --- | --- | | First name, last name | Yes | | Date of birth | Yes | | Email | Yes | | Phone number, IP address | Optional | | Country, address line 1, city, state/province/region, postal code | Yes | | Address line 2 | Optional | | Tax ID (government ID number) | Yes | | ID document: country, type (`PASSPORT`, `ID_CARD`, or `DRIVERS`), front file | Yes | | ID document: back file | Optional | | Proof of address: type (`UTILITY_BILL`, `BANK_STATEMENT`, `RENTAL_AGREEMENT`, `TAX_DOCUMENT`, `GOVERNMENT_CORRESPONDENCE`), file | Optional | | Selfie file | Yes | ### KYC Enhanced (individual) Everything from KYC Standard, plus mandatory: * Proof of address: type and file (optional on Standard, required here) * Source of funds document type and file * Purpose of transactions (`purpose_of_transactions_explanation` becomes required if you send `other`) * Selfie file ### KYB Standard (business) | Field | Required | | --- | --- | | Legal name, tax ID, formation date | Yes | | Email, country, address line 1 | Yes | | Website | Yes | | Alternate name (doing business as), phone number, IP address, address line 2 | Optional | | Incorporation document | Yes | | Proof of ownership document | Yes | | Proof of address: type, file | Yes | | Owners (UBOs and controlling persons) | Yes, at least 1 | Each owner provides the same fields as a Standard KYC individual (excluding phone number and IP address), plus a `role` (`beneficial_controlling`, `beneficial_owner`, or `controlling_person`) and optional `ownership_percentage` and `title`. Unlike the primary individual, where proof of address is optional, owners must provide proof of address (type and file). Owners with `country: "US"` must also send `tax_type` (`SSN` or `ITIN`) matching the format of their `tax_id`. All customers from high-risk countries must go through Enhanced KYC. See the document quality guidance below before you submit files for either level. ## Customer statuses Every customer has a `kyc_status` indicating the current state of its verification: | Status | Meaning | | --- | --- | | `verifying` | KYC is being processed. This is the status on creation. | | `approved` | KYC verified and approved. | | `rejected` | KYC rejected (final). | | `compliance_request` | The compliance team opened a Request for Information (RFI) and is waiting on additional documents or clarification. The customer is paused. See [Requests for information](#requests-for-information). | | `approved_rfi` | The customer is approved and fully operational, but the compliance team opened an RFI they still need to answer. See [Approved with an open RFI](#approved-with-an-open-rfi). | The update timeline depends on the KYC type: * **KYC Standard**: after ~60 seconds the status typically becomes `approved` or `rejected` automatically. When the compliance team needs to review manually, it stays `verifying` until they decide. * **KYC Enhanced and KYB Standard**: always require manual review, so the status stays `verifying` until review completes. When KYC is rejected, BlindPay returns feedback in the `kyc_warnings` or `fraud_warnings` field explaining what to correct. We recommend creating a brand new customer with the corrected fields rather than trying to fix a `rejected` customer in place. ## Limits Limits are calculated on the stablecoin amount transferred. Each customer has separate limits for payouts (sending) and payins (receiving). | | KYC Standard | KYB Standard | KYC Enhanced | | --- | --- | --- | --- | | Per transaction | $10,000 | $30,000 | $50,000 | | Daily | $50,000 | $100,000 | $100,000 | | Monthly | $100,000 | $250,000 | $500,000 | These limits are set for compliance purposes. They can be increased upon submission of additional documentation using the limit-increase endpoint. ## Document quality High-quality documents avoid delays and follow-up requests. ### Accepted formats * PDF, JPEG, or PNG (WEBP and HEIC/HEIF photos are also accepted and converted automatically) * Maximum file size: 5 MB * The entire document must be visible: no cropped edges ### Photo guidelines * Photograph the original physical document, not a screen or printed copy * Ensure all text is clear and readable with no glare * Use good lighting and keep the image in focus * Submit both front and back if the document has two sides ### Common rejection causes * Screenshots of documents * Photos taken of a screen or monitor * Blurry, dark, or cropped images * Printed or photocopied documents ## Terms of service Customers must accept BlindPay's terms of service before you can create them. The terms can only be accepted by a user accessing `https://app.blindpay.com` on the client side; requests from servers are ignored. ### Generate a terms of service URL The API only accepts a uuid on the idempotency\_key field. Reusing the same key on a second request is rejected. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/e/instances/in_000000000000/tos \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "idempotency_key": "" }' ``` The response is a URL with the following query parameters: ```bash [URL example] https://app.blindpay.com/e/terms-of-service?session_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...&idempotency_key=5d8b149e-a55d-4b5b-a8f8-7c4fa315f854&redirect_url= ``` | Param | Required | Example | | --- | --- | --- | | `session_token` | Yes | JWT | | `idempotency_key` | Yes | uuid | | `redirect_url` | No | `https://yourapp.com/` | | `customer_id` | No | `re_000000000000` (required when accepting a new TOS version) | We strongly recommend adding a `redirect_url` parameter so the customer lands back in your application after accepting. ### Accept the terms of service Open the generated URL for the customer to accept on `https://app.blindpay.com`. After acceptance, BlindPay redirects to your `redirect_url` and appends a `tos_id` query parameter. Copy that `tos_id`: you'll pass it as `tos_id` when you create the customer. You also receive a `tos.accept` webhook event when the terms of service is accepted. ### Versioning Each `tos_id` is tied to the terms of service version that was active when it was generated, and it can only ever be linked to one customer. If BlindPay updates the terms of service, calls to the payout quote or payin quote endpoints return an error with message `please_accept_terms_of_service` for customers whose acceptance predates the new version. Generate a new terms of service URL, set `customer_id` on it, and have the customer accept again. Once accepted, the quote endpoints stop returning the error. ## Requests for information A Request for Information (RFI) is how the compliance team asks for missing or clarifying details when a customer's KYC or KYB review is incomplete. Instead of rejecting the application, BlindPay pauses it, attaches a list of fields the customer needs to fill in, and notifies you so you can collect the response. This is what the `compliance_request` status (above) means. While a customer is in `compliance_request`, payouts and payins cannot be created for them. There can only be one open RFI per customer at a time; if compliance needs another round, a new RFI is created after the previous one is reviewed. ### Approved with an open RFI Compliance can also open an RFI without pausing the customer. In that case the customer's `kyc_status` becomes `approved_rfi` instead of `compliance_request`: the customer stays fully operational (payouts, payins, and virtual accounts keep working) while the RFI is open. This is typically used for periodic reviews or follow-up questions on customers that are already approved. The lifecycle differs from the blocking RFI in three ways: | | `compliance_request` | `approved_rfi` | | --- | --- | --- | | While the RFI is open | Payouts and payins are blocked | Customer keeps full access | | After the response is submitted | `verifying`, then compliance re-reviews | `approved` immediately, no re-review | | If the 27-day deadline passes | Customer is auto-rejected | Customer is **not** auto-rejected; compliance reviews the case manually | The fetch and submit endpoints below work the same for both types, and `customer.update` webhooks fire on every transition, so no integration change is needed to support `approved_rfi`. ### Deadline When an RFI is opened, the customer has 27 days to respond. If no submission arrives within that window, BlindPay automatically rejects the customer. The deadline is included as `expires_at` in the RFI payload, and every new RFI starts a fresh window. ### RFI status | Status | Meaning | | --- | --- | | `pending` | The RFI is open and waiting for a response. The customer is in `compliance_request` (or `approved_rfi` for the non-blocking type). | | `submitted` | The response has been received. The customer is back in `verifying` (or `approved` if they were in `approved_rfi`). | | `expired` | The 27-day deadline passed without a submission. The customer was auto-rejected (customers in `approved_rfi` are not auto-rejected; compliance reviews them manually). | | `cancelled` | Compliance cancelled the RFI. The customer was restored to its prior status. | ### Receive the webhook Subscribe to the [`customer.update` webhook](/docs/learn/webhooks). When a customer enters or leaves `compliance_request`, you receive a payload with the new status: ```json { "webhook_event": "customer.update", "id": "re_000000000000", "instance_id": "in_000000000000", "kyc_status": "compliance_request", "first_name": "John", "last_name": "Doe" } ``` ### Fetch the open RFI ```bash [cURL] curl --request GET \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/rfi \ --header 'Authorization: Bearer YOUR_API_KEY' ``` Returns the open RFI, or `404` if none is open for this customer: ```json { "id": "rfi_a1b2c3d4e5f6", "customer_id": "re_000000000000", "instance_id": "in_000000000000", "status": "pending", "request": [ { "title": "Business description", "description": "The business description doesn't match the company's website. Please provide a clarification.", "fields": [ { "key": "business_description", "label": "Business description", "required": true }, { "key": "business_description_explanation", "label": "Explanation", "required": true } ] }, { "title": "Proof of address", "description": "The proof of address attached doesn't match the address provided. Please upload a new one.", "fields": [ { "key": "proof_of_address_doc_type", "label": "Proof of address type", "required": true, "items": [ { "label": "Utility bill", "value": "UTILITY_BILL" }, { "label": "Bank statement", "value": "BANK_STATEMENT" } ] }, { "key": "proof_of_address_doc_file", "label": "Proof of address file", "required": true, "regex": "^https://[^\\s]+$" } ] } ], "response": {}, "expires_at": "2026-06-08T13:00:00.000Z", "submitted_at": null, "created_at": "2026-05-12T13:00:00.000Z" } ``` The `request` field is an array of sections. Each section has a `title`, a `description` written by compliance, and one or more `fields` the customer must fill in: | Property | Type | Description | | --- | --- | --- | | `key` | string | Unique key within the RFI. Use this key in the response body. | | `label` | string | Label to show above the input. | | `required` | boolean | If `true`, the field must be present and non-empty in the response. | | `regex` | string | Optional. A pattern the response value must match. | | `items` | array | Optional. If present, the field is a dropdown and the response must be one of the provided values. | | `multiple` | boolean | Optional. If `true`, the field accepts an array of URLs (multiple file uploads, max 20). | For any file upload field, use the [upload endpoint](https://api.blindpay.com/reference#tag/upload/POST/v1/upload) to host the file and submit the resulting URL. ### Submit a response The response is a flat object keyed by each field's `key`. There is no `rfi_id` in the URL because there's only ever one open RFI per customer. The submission is single-shot. All required fields must be included in one request; there is no partial save, so a submission missing required fields is rejected with 400. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/rfi \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "business_description": "We sell B2B SaaS payroll software to mid-market companies.", "business_description_explanation": "Updated description matches our public website.", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://files.blindpay.com/1767022801827-bill.pdf" }' ``` ```json { "success": true } ``` The body is validated dynamically against the stored fields: required fields must be present and non-empty, `regex` is applied as a pattern test, `multiple: true` requires an array of URLs (max 20), and unknown keys are rejected. A validation failure returns `400` with details about the offending key. Once submitted, `customer.update` fires with `kyc_status: "verifying"` while BlindPay re-reviews. After re-review you receive one of `approved`, `rejected`, or `compliance_request` again if a new RFI was opened, in which case you repeat from receiving the webhook. If the 27-day window elapses without a submission, `customer.update` fires with `kyc_status: "rejected"` for the auto-rejection. For an `approved_rfi` customer the flow is shorter: once the response is submitted, `customer.update` fires with `kyc_status: "approved"` directly, with no re-review step. Missing the deadline does not auto-reject the customer. ## Uploading documents Use the [upload endpoint](https://api.blindpay.com/reference#tag/upload/POST/v1/upload) to turn a customer's KYC documents and pictures into the file URLs the customer and RFI endpoints expect. BlindPay encrypts files before sharing them with vendors or saving them in the database. ```bash [cURL] curl 'https://api.blindpay.com/v1/upload?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --form 'bucket=onboarding' \ --form 'file=your_file.pdf' ``` Use the `file_url` returned by the API to populate document fields when creating a customer or submitting an RFI response. You can optionally pre-screen a document before submitting it: the [document analysis endpoint](https://api.blindpay.com/reference#tag/upload/POST/v1/upload/analyze) reads a PDF, JPG, or PNG and returns an `approval_rate` of `low`, `medium`, or `high` plus a short reason, so you can prompt for a better file before it reaches the official KYC review. It is a signal, not a decision; the official outcome still comes from the verification flow. ## Gotchas * **Duplicate tax ID is blocked on production.** On production instances, creating a customer with a `tax_id` that matches an existing, already-`approved` customer in the same instance is rejected. This check does not run on development instances. Use a different tax ID or update the existing approved customer's other attachments (bank accounts, wallets) instead of creating a duplicate. * **Recommended: create a new customer instead of editing a rejected one.** For a `rejected` customer, we recommend creating a new customer with the corrected fields rather than retrying in place. * **On development instances, customers auto-approve.** Set the first name (individuals) or legal name (businesses) to `Fail` to force a rejection for testing. You can use placeholder URLs for document fields on development. ## Related * [Webhooks](/docs/learn/webhooks): subscribe to `customer.update` and `tos.accept` * [Instances](/docs/learn/instances): development vs. production behavior * [Sandbox vs. production](/docs/learn/sandbox-vs-production): full testing checklist * [Cut-off times](/docs/kb/cut-off-times): KYC and onboarding review SLAs * [Supported countries](/docs/kb/supported-countries): high-risk and prohibited country lists --- --- url: /docs/kb/kyb-documents.md description: >- Document requirements for business KYB verification — formation docs, ownership proof, UBO identification, and proof of address. --- ## Summary KYB (Know Your Business) verification requires document-level proof beyond the core business fields BlindPay collects at customer creation. You must submit corporate formation documents, an ownership structure document identifying all UBOs, proof of business address, and government ID plus proof of residence for each UBO. Companies in higher-risk sectors may face enhanced due diligence. ## Required business fields The core business and tax fields collected during customer creation (legal name, tax ID, formation date, registered address, website, UBOs) are documented in [Customers — Required fields](/docs/kb/kyc#required-fields). This page covers only the supporting **documents** KYB uniquely requires. `tax_id` must contain the beneficiary's local tax ID based on their country of residence (e.g. CPF for Brazil). For **US residents**, this field must always be the **SSN**. ## Corporate formation documents * **Articles of Organization** (or local equivalent) — full document required (all pages). * **Certificate of Incorporation** (or equivalent) — full document required (all pages). **Important**: Submitting only the first page is not sufficient. All pages of each document must be included. ## Ownership structure documentation A **Share Register** or an official legal document clearly showing the full Beneficial Ownership structure is required. This document must clearly identify: * All shareholders * Ownership percentages * Ultimate Beneficial Owners (UBOs), including indirect ownership where applicable **Note**: BlindPay may request an ownership chart if the structure is complex. ## Proof of business address Accepted documents include a utility bill, lease agreement, or bank statement. The document must: * Be dated within the last **90 days** * Clearly display the company's **legal entity name** and **registered address** * Be complete and fully visible For the full list of accepted documents and submission guidelines, see the [Proof of Address](/docs/kb/proof-of-address) guide. ## UBO documentation For each Ultimate Beneficial Owner (UBO), BlindPay requires government-issued identification and proof of residence. ### Government-issued identification One of the following is accepted: Driver's License, National ID, Residence Permit, or Passport. Requirements: * Within its official validity period and not issued more than **10 years** prior to submission * Clear, complete, and fully visible * All four corners visible where applicable * Information must match the ownership documentation provided ### Proof of residence * Dated within the last **90 days** * Clearly shows the UBO's **full name** and **residential address** * Issued by a recognized institution (e.g., utility bill, bank statement, or official government correspondence) ## Virtual Account additional information Virtual Account requests undergo a separate evaluation with their own documentation. See the [Virtual Accounts](/docs/kb/virtual-accounts) guide for industry type, nature of business, purpose of account, and source of funds/wealth requirements. ## Enhanced due diligence sectors **Enhanced due diligence**: Additional documentation may be requested for companies operating in the sectors listed below. | Sector | Examples | | --- | --- | | **Banking & Lending** | Banks, credit unions, and lending institutions | | **Securities & Trading** | Securities brokers, forex dealers, and trading platforms | | **Money Services** | Money services businesses (MSBs) and remittance companies | | **Fintech** | Payments, digital wallets, and acquirers | | **Currency Exchange** | Currency exchange houses | | **Digital Assets** | Cryptocurrency exchanges, virtual asset custodians, ICOs, STOs, token fundraising activities | | **DeFi** | DeFi platforms, staking and lending protocols | | **High-Value Goods** | Jewelry, art, antiques, luxury cars, yachts, or aircraft dealers | | **Real Estate** | Real estate developers and real estate transactions | | **Natural Resources** | Oil, gas, and mining companies (including rare minerals) | | **Regulated Industries** | Chemicals and pharmaceuticals (dual-use), private security companies | | **Non-Profits** | NGOs, charities, and foundations | | **High-Risk Jurisdictions** | Companies operating in FATF high-risk jurisdictions and offshore companies (e.g., Cayman Islands, Panama, Monaco, among others) | ## Related * [Customers](/docs/kb/kyc) · [Virtual Accounts](/docs/kb/virtual-accounts) * [Proof of Address](/docs/kb/proof-of-address) · [Source of Funds & Source of Wealth](/docs/kb/source-of-funds) --- --- url: /docs/kb/proof-of-address.md description: >- Accepted proof-of-address documents and submission requirements for business and individual verification. --- ## Summary Proof of address confirms where a business operates and where its key individuals live. It may be requested during business verification, individual verification of UBOs or authorized persons, and ongoing compliance reviews. Documents must show a physical address, the full legal name, and be issued within the last 90 days. ## When it is required You may need to provide proof of address in the following situations: * **Business verification** — confirming the registered or operating address of your company. * **Individual verification** — confirming the residential address of Ultimate Beneficial Owners (UBOs), Authorized Persons, or other key individuals tied to the account. * **Ongoing reviews** — occasionally, we may request updated documents during periodic compliance checks. ## Business proof of address Your business proof of address document must meet the following requirements: * Confirms the **current registered or operating address** of the company * Is issued in the **legal name of the business** (exactly as registered) * Was issued **within the last 90 days** (with some exceptions noted below) * Shows a **physical address** — PO boxes and virtual addresses are not accepted ### Accepted business documents We accept the following documents for business address verification: * Bank statement in the business name * Utility bill for the business premises (electricity, water, gas, internet) * Government-issued correspondence addressed to the business * Office or commercial lease agreement (must be current — may be older than 90 days) * Business license that displays the company address * Company registry extract or registration certificate * Tax authority correspondence showing the business address ## Individual proof of address For individual address verification, the document must: * Confirm the **current residential address** of the person * Be issued in the **individual's full legal name**, matching the name provided during onboarding * Have been issued **within the last 90 days** (unless otherwise noted) * Show a **physical residential address** ### Accepted individual documents We accept the following documents for personal address verification: * Utility bills (electricity, water, gas, internet, or landline) * Bank or credit card statements * Government-issued letters (tax notices, social benefit correspondence) * Residence registration or certificate of domicile * Residential lease or tenancy agreement (must be current) * Local authority or municipal tax bills * Insurance policies that show a residential address (home, health, or vehicle) * Driver's license or national ID card — as long as it includes a full residential address and is still valid ## Document quality A complete, high-quality submission avoids delays and follow-up requests. ### Your document must clearly show * The **full legal name** (of the business or individual) * The **complete address** * The **date of issue** * The **issuer's name or logo** (bank, utility company, government body, etc.) ### Submission format * Upload a **clear, color scan or photo** of the document * Accepted formats: **PDF, JPEG, or PNG** * Maximum file size: **3 MB** * The **entire document** must be visible — no cropped or partial images ### Photo tips * Photograph the **original physical document** — not a screen or printed copy. * Make sure all text is **clear and readable**, with no glare or reflections. * Capture the **entire document** in the frame — don't crop any edges. * Use **good lighting** and keep the image **in focus**. * If applicable, send **both the front and back** of the document. ### Avoid submitting * Photos taken of a screen, monitor, or another device * Screenshots, or pictures of printed or photocopied documents * Blurry, dark, cropped, or unclear images **Note:** Screenshots are not accepted unless explicitly approved by our team. ## Related * [Customers](/docs/kb/kyc#required-fields) · [KYC Basics](/docs/kb/kyc-basics#document-quality) --- --- url: /docs/kb/source-of-funds.md description: >- Documentation required to verify the source of funds and source of wealth for your business. --- ## Summary Source of funds describes how the funds used on BlindPay are generated; source of wealth describes how a business or its owners accumulated their overall wealth. Either may be requested during onboarding or ongoing reviews when an account is subject to enhanced due diligence. Documents must be issued in the business name and clearly show the source and flow of funds. ## Source of funds vs. source of wealth * **Source of funds** describes how the funds you'll use on BlindPay are generated. For example: operating revenue, customer payments, or investment capital. * **Source of wealth** describes how the business or its owners originally accumulated their overall wealth. For example: business earnings, prior ventures, or long-term investments. Both may be requested during onboarding or as part of an ongoing review. ## When it is required We may ask for source of funds or source of wealth documentation in the following situations: * Your account is subject to enhanced due diligence based on a risk assessment * Your funding sources need additional validation, such as high transaction volumes or complex funding structures * There are significant changes in your expected activity, ownership, or business model * Additional verification is needed to meet regulatory or compliance obligations If documentation is required, we'll let you know clearly during onboarding or through a follow-up request. ## Acceptable documentation The documents you provide should clearly show where the funds come from, how they were generated, and how they will be transferred to BlindPay. Everything should align with your declared business activities. ### Fiat-based funding sources For funds coming from traditional financial sources, we accept: * **Bank statements or account summaries** showing the account from which funds will be transferred, issued by a regulated financial institution * **Investment or capital contribution records** such as subscription agreements, capital injections, or founder contribution confirmations * **Revenue-generating documentation** like invoices, customer contracts, settlement statements, or receipts demonstrating operating income * **Loan or financing agreements** showing lawful borrowing through executed loan agreements or credit facilities * **Asset sale or liquidation records** evidencing proceeds from the sale of business assets, securities, or property ### Digital asset and crypto-based funding sources For businesses operating in the crypto and digital asset ecosystem, we accept: * **Wallet ownership evidence** proving the business or its principals control the wallet(s) from which digital assets will be transferred * **On-chain transaction history** showing the origin and movement of funds, including transaction hashes and wallet addresses * **Exchange account statements** from regulated exchanges showing balances, deposits, withdrawals, or trading activity * **Stablecoin issuance or redemption records** showing how stablecoins were acquired, redeemed, or minted * **Market-making or trading records** such as trade history, P\&L summaries, or liquidity provision records * **Token sale or fundraising documentation** including allocation summaries, private placement records, and use-of-funds explanations ## Document standards To avoid delays, make sure your documents meet these requirements: * Issued in the **name of the business** or relevant funding entity * Clearly shows the **source and flow of funds** * **Legible and complete**, with no material redactions * Submitted in an accepted digital format: **PDF, JPG, or PNG** We may request additional supporting documentation if we need further clarification or validation. ## Related * [Customers](/docs/kb/kyc#required-fields) · [Proof of Address](/docs/kb/proof-of-address) --- --- url: /docs/kb/naics-codes.md description: Find the NAICS industry code for your business during BlindPay onboarding. --- ## Summary NAICS (North American Industry Classification System) codes classify businesses by their primary economic activity. BlindPay uses NAICS 2022 codes to classify the industries of businesses on our platform, and you'll need to provide your industry code during onboarding. Below is the full list of accepted industry codes, organized by industry. If your industry isn't listed, contact and we'll add the appropriate code for you. ## Agriculture | NAICS Code | Industry Description | | ---------- | ------------------------------------------------- | | 111110 | Soybean Farming | | 111120 | Oilseed Except Soybean Farming | | 111130 | Dry Pea And Bean Farming | | 111140 | Wheat Farming | | 111150 | Corn Farming | | 111160 | Rice Farming | | 111191 | Oilseed And Grain Combination Farming | | 111199 | All Other Grain Farming | | 111211 | Potato Farming | | 111219 | Other Vegetable Except Potato And Melon Farming | | 111310 | Orange Groves | | 111320 | Citrus Except Orange Groves | | 111331 | Apple Orchards | | 111332 | Grape Vineyards | | 111333 | Strawberry Farming | | 111334 | Berry Except Strawberry Farming | | 111335 | Tree Nut Farming | | 111336 | Fruit And Tree Nut Combination Farming | | 111339 | Other Noncitrus Fruit Farming | | 111411 | Mushroom Production | | 111419 | Other Food Crops Under Cover | | 111421 | Nursery And Tree Production | | 111422 | Floriculture Production | | 111910 | Tobacco Farming | | 111920 | Cotton Farming | | 111930 | Sugarcane Farming | | 111940 | Hay Farming | | 111991 | Sugar Beet Farming | | 111992 | Peanut Farming | | 111998 | All Other Miscellaneous Crop Farming | | 112111 | Beef Cattle Ranching And Farming | | 112112 | Cattle Feedlots | | 112120 | Dairy Cattle And Milk Production | | 112210 | Hog And Pig Farming | | 112310 | Chicken Egg Production | | 112320 | Broilers And Other Meat Type Chicken Production | | 112330 | Turkey Production | | 112340 | Poultry Hatcheries | | 112390 | Other Poultry Production | | 112410 | Sheep Farming | | 112420 | Goat Farming | | 112511 | Finfish Farming And Fish Hatcheries | | 112512 | Shellfish Farming | | 112519 | Other Aquaculture | | 112910 | Apiculture | | 112920 | Horses And Other Equine Production | | 112930 | Fur Bearing Animal And Rabbit Production | | 112990 | All Other Animal Production | | 113110 | Timber Tract Operations | | 113210 | Forest Nurseries And Gathering Of Forest Products | | 113310 | Logging | | 114111 | Finfish Fishing | | 114112 | Shellfish Fishing | | 114119 | Other Marine Fishing | | 114210 | Hunting And Trapping | | 115111 | Cotton Ginning | | 115112 | Soil Preparation Planting And Cultivating | | 115113 | Crop Harvesting Primarily By Machine | | 115114 | Postharvest Crop Activities Except Cotton Ginning | | 115115 | Farm Labor Contractors And Crew Leaders | | 115116 | Farm Management Services | | 115210 | Support Activities For Animal Production | | 115310 | Support Activities For Forestry | ## Business Services | NAICS Code | Industry Description | | ---------- | -------------------------------------------------------------------- | | 541211 | Offices Of Certified Public Accountants | | 541213 | Tax Preparation Services | | 541214 | Payroll Services | | 541219 | Other Accounting Services | | 541410 | Interior Design Services | | 541420 | Industrial Design Services | | 541430 | Graphic Design Services | | 541490 | Other Specialized Design Services | | 541513 | Computer Facilities Management Services | | 541611 | Administrative Management And General Management Consulting Services | | 541612 | Human Resources Consulting Services | | 541613 | Marketing Consulting Services | | 541614 | Process Physical Distribution And Logistics Consulting Services | | 541618 | Other Management Consulting Services | | 541620 | Environmental Consulting Services | | 541690 | Other Scientific And Technical Consulting Services | | 541713 | Research And Development In Nanotechnology | | 541714 | Research And Development In Biotechnology | | 541715 | Research And Development In Physical Engineering And Life Sciences | | 541720 | Research And Development In Social Sciences And Humanities | | 541810 | Advertising Agencies | | 541820 | Public Relations Agencies | | 541830 | Media Buying Agencies | | 541840 | Media Representatives | | 541850 | Indoor And Outdoor Display Advertising | | 541860 | Direct Mail Advertising | | 541870 | Advertising Material Distribution Services | | 541890 | Other Services Related To Advertising | | 541910 | Marketing Research And Public Opinion Polling | | 541930 | Translation And Interpretation Services | | 541990 | All Other Professional Scientific And Technical Services | | 561110 | Office Administrative Services | | 561210 | Facilities Support Services | | 561311 | Employment Placement Agencies | | 561312 | Executive Search Services | | 561320 | Temporary Help Services | | 561330 | Professional Employer Organizations | | 561410 | Document Preparation Services | | 561421 | Telephone Answering Services | | 561422 | Telemarketing Bureaus And Other Contact Centers | | 561431 | Private Mail Centers | | 561439 | Other Business Service Centers Including Copy Shops | | 561440 | Collection Agencies | | 561450 | Credit Bureaus | | 561491 | Repossession Services | | 561492 | Court Reporting And Stenotype Services | | 561499 | All Other Business Support Services | | 561611 | Investigation And Personal Background Check Services | | 561612 | Security Guards And Patrol Services | | 561613 | Armored Car Services | | 561621 | Security Systems Services Except Locksmiths | | 561622 | Locksmiths | | 561710 | Exterminating And Pest Control Services | | 561720 | Janitorial Services | | 561790 | Other Services To Buildings And Dwellings | | 561910 | Packaging And Labeling Services | | 561920 | Convention And Trade Show Organizers | | 561990 | All Other Support Services | ## Construction | NAICS Code | Industry Description | | ---------- | -------------------------------------------------------------------------------- | | 236115 | New Single Family Housing Construction Except For Sale Builders | | 236116 | New Multifamily Housing Construction Except For Sale Builders | | 236117 | New Housing For Sale Builders | | 236118 | Residential Remodelers | | 236210 | Industrial Building Construction | | 236220 | Commercial And Institutional Building Construction | | 237110 | Water And Sewer Line And Related Structures Construction | | 237120 | Oil And Gas Pipeline And Related Structures Construction | | 237130 | Power And Communication Line And Related Structures Construction | | 237210 | Land Subdivision | | 237310 | Highway Street And Bridge Construction | | 237990 | Other Heavy And Civil Engineering Construction | | 238110 | Poured Concrete Foundation And Structure Contractors | | 238111 | Residential Poured Concrete Foundation And Structure Contractors | | 238112 | Non Residential Poured Concrete Foundation And Structure Contractors | | 238121 | Residential Structural Steel And Precast Concrete Contractors | | 238122 | Non Residential Structural Steel And Precast Concrete Contractors | | 238131 | Residential Framing Contractors | | 238132 | Non Residential Framing Contractors | | 238140 | Masonry Contractors | | 238141 | Residential Masonry Contractors | | 238142 | Non Residential Masonry Contractors | | 238151 | Residential Glass And Glazing Contractors | | 238152 | Non Residential Glass And Glazing Contractors | | 238160 | Roofing Contractors | | 238161 | Residential Roofing Contractors | | 238162 | Non Residential Roofing Contractors | | 238171 | Residential Siding Contractors | | 238172 | Non Residential Siding Contractors | | 238191 | Residential Other Foundation Structure And Building Exterior Contractors | | 238192 | Non Residential Other Foundation Structure And Building Exterior Contractors | | 238210 | Electrical Contractors And Other Wiring Installation Contractors | | 238211 | Residential Electrical Contractors And Other Wiring Installation Contractors | | 238212 | Non Residential Electrical Contractors And Other Wiring Installation Contractors | | 238220 | Plumbing Heating And Air Conditioning Contractors | | 238221 | Residential Plumbing Heating And Air Conditioning Contractors | | 238222 | Non Residential Plumbing Heating And Air Conditioning Contractors | | 238291 | Residential Other Building Equipment Contractors | | 238292 | Non Residential Other Building Equipment Contractors | | 238311 | Residential Drywall And Insulation Contractors | | 238312 | Non Residential Drywall And Insulation Contractors | | 238321 | Residential Painting And Wall Covering Contractors | | 238322 | Non Residential Painting And Wall Covering Contractors | | 238331 | Residential Flooring Contractors | | 238332 | Non Residential Flooring Contractors | | 238341 | Residential Tile And Terrazzo Contractors | | 238342 | Non Residential Tile And Terrazzo Contractors | | 238350 | Finish Carpentry Contractors | | 238351 | Residential Finish Carpentry Contractors | | 238352 | Non Residential Finish Carpentry Contractors | | 238391 | Residential Other Building Finishing Contractors | | 238392 | Non Residential Other Building Finishing Contractors | | 238911 | Residential Site Preparation Contractors | | 238912 | Non Residential Site Preparation Contractors | | 238990 | All Other Specialty Trade Contractors | | 238991 | Residential All Other Specialty Trade Contractors | | 238992 | Non Residential All Other Specialty Trade Contractors | | 541310 | Architectural Services | | 541320 | Landscape Architectural Services | | 541330 | Engineering Services | | 541340 | Drafting Services | | 541350 | Building Inspection Services | | 541360 | Geophysical Surveying And Mapping Services | | 541370 | Surveying And Mapping Except Geophysical Services | | 541380 | Testing Laboratories And Services | ## Consumer Services | NAICS Code | Industry Description | | ---------- | --------------------------------------------------------------------------- | | 532111 | Passenger Car Rental | | 532112 | Passenger Car Leasing | | 532120 | Truck Utility Trailer And RV Rental And Leasing | | 532210 | Consumer Electronics And Appliances Rental | | 532281 | Formal Wear And Costume Rental | | 532282 | Video Tape And Disc Rental | | 532283 | Home Health Equipment Rental | | 532284 | Recreational Goods Rental | | 532289 | All Other Consumer Goods Rental | | 532310 | General Rental Centers | | 532411 | Commercial Air Rail And Water Transportation Equipment Rental And Leasing | | 532412 | Construction Mining And Forestry Machinery And Equipment Rental And Leasing | | 532420 | Office Machinery And Equipment Rental And Leasing | | 532490 | Other Commercial And Industrial Machinery And Equipment Rental And Leasing | | 533110 | Lessors Of Nonfinancial Intangible Assets Except Copyrighted Works | | 541921 | Photography Studios Portrait | | 541922 | Commercial Photography | | 561730 | Landscaping Services | | 561740 | Carpet And Upholstery Cleaning Services | | 624110 | Child And Youth Services | | 624120 | Services For The Elderly And Persons With Disabilities | | 624190 | Other Individual And Family Services | | 624210 | Community Food Services | | 624221 | Temporary Shelters | | 624229 | Other Community Housing Services | | 624230 | Emergency And Other Relief Services | | 624310 | Vocational Rehabilitation Services | | 624410 | Child Care Services | | 811111 | General Automotive Repair | | 811114 | Specialized Automotive Repair | | 811121 | Automotive Body Paint And Interior Repair And Maintenance | | 811122 | Automotive Glass Replacement Shops | | 811191 | Automotive Oil Change And Lubrication Shops | | 811192 | Car Washes | | 811198 | All Other Automotive Repair And Maintenance | | 811210 | Electronic And Precision Equipment Repair And Maintenance | | 811310 | Commercial And Industrial Machinery And Equipment Repair And Maintenance | | 811411 | Home And Garden Equipment Repair And Maintenance | | 811412 | Appliance Repair And Maintenance | | 811420 | Reupholstery And Furniture Repair | | 811430 | Footwear And Leather Goods Repair | | 811490 | Other Personal And Household Goods Repair And Maintenance | | 812111 | Barber Shops | | 812112 | Beauty Salons | | 812113 | Nail Salons | | 812191 | Diet And Weight Reducing Centers | | 812199 | Other Personal Care Services | | 812210 | Funeral Homes And Funeral Services | | 812220 | Cemeteries And Crematories | | 812310 | Coin Operated Laundries And Drycleaners | | 812320 | Drycleaning And Laundry Services Except Coin Operated | | 812331 | Linen Supply | | 812332 | Industrial Launderers | | 812910 | Pet Care Except Veterinary Services | | 812921 | Photofinishing Laboratories Except One Hour | | 812922 | One Hour Photofinishing | | 812930 | Parking Lots And Garages | | 812990 | All Other Personal Services | | 814110 | Private Households | ## Education | NAICS Code | Industry Description | | ---------- | ------------------------------------------------ | | 611110 | Elementary And Secondary Schools | | 611210 | Junior Colleges | | 611310 | Colleges Universities And Professional Schools | | 611410 | Business And Secretarial Schools | | 611420 | Computer Training | | 611430 | Professional And Management Development Training | | 611511 | Cosmetology And Barber Schools | | 611512 | Flight Training | | 611513 | Apprenticeship Training | | 611519 | Other Technical And Trade Schools | | 611610 | Fine Arts Schools | | 611620 | Sports And Recreation Instruction | | 611630 | Language Schools | | 611691 | Exam Preparation And Tutoring | | 611692 | Automobile Driving Schools | | 611699 | All Other Miscellaneous Schools And Instruction | | 611710 | Educational Support Services | ## Energy, Utilities, Waste & Minerals | NAICS Code | Industry Description | | ---------- | --------------------------------------------------------------- | | 211120 | Crude Petroleum Extraction | | 211130 | Natural Gas Extraction | | 212114 | Surface Coal Mining | | 212115 | Underground Coal Mining | | 212210 | Iron Ore Mining | | 212220 | Gold Ore And Silver Ore Mining | | 212230 | Copper Nickel Lead And Zinc Mining | | 212290 | Other Metal Ore Mining | | 212311 | Dimension Stone Mining And Quarrying | | 212312 | Crushed And Broken Limestone Mining And Quarrying | | 212313 | Crushed And Broken Granite Mining And Quarrying | | 212319 | Other Crushed And Broken Stone Mining And Quarrying | | 212321 | Construction Sand And Gravel Mining | | 212322 | Industrial Sand Mining | | 212323 | Kaolin Clay And Ceramic And Refractory Minerals Mining | | 212390 | Other Nonmetallic Mineral Mining And Quarrying | | 213111 | Drilling Oil And Gas Wells | | 213112 | Support Activities For Oil And Gas Operations | | 213113 | Support Activities For Coal Mining | | 213114 | Support Activities For Metal Mining | | 213115 | Support Activities For Nonmetallic Minerals Except Fuels Mining | | 221111 | Hydroelectric Power Generation | | 221112 | Fossil Fuel Electric Power Generation | | 221113 | Nuclear Electric Power Generation | | 221114 | Solar Electric Power Generation | | 221115 | Wind Electric Power Generation | | 221116 | Geothermal Electric Power Generation | | 221117 | Biomass Electric Power Generation | | 221118 | Other Electric Power Generation | | 221121 | Electric Bulk Power Transmission And Control | | 221122 | Electric Power Distribution | | 221210 | Natural Gas Distribution | | 221310 | Water Supply And Irrigation Systems | | 221320 | Sewage Treatment Facilities | | 221330 | Steam And Air Conditioning Supply | | 562111 | Solid Waste Collection | | 562112 | Hazardous Waste Collection | | 562119 | Other Waste Collection | | 562211 | Hazardous Waste Treatment And Disposal | | 562212 | Solid Waste Landfill | | 562213 | Solid Waste Combustors And Incinerators | | 562219 | Other Nonhazardous Waste Treatment And Disposal | | 562910 | Remediation Services | | 562920 | Materials Recovery Facilities | | 562991 | Septic Tank And Related Services | | 562998 | All Other Miscellaneous Waste Management Services | ## Finance, Insurance | NAICS Code | Industry Description | | ---------- | -------------------------------------------------------------------------------- | | 521110 | Monetary Authorities Central Bank | | 522110 | Commercial Banking | | 522130 | Credit Unions | | 522180 | Savings Institutions And Other Depository Credit Intermediation | | 522210 | Credit Card Issuing | | 522220 | Sales Financing | | 522291 | Consumer Lending | | 522292 | Real Estate Credit | | 522298 | All Other Nondepository Credit Intermediation | | 522299 | International Secondary Market And All Other Nondepository Credit Intermediation | | 522310 | Mortgage And Nonmortgage Loan Brokers | | 522320 | Financial Transactions Processing | | 522390 | Other Activities Related To Credit Intermediation | | 523150 | Investment Banking And Securities Intermediation | | 523160 | Commodity Contracts Intermediation | | 523210 | Securities And Commodity Exchanges | | 523910 | Miscellaneous Intermediation | | 523940 | Portfolio Management And Investment Advice | | 523991 | Trust Fiduciary And Custody Activities | | 523999 | Other Financial Investment Activities | | 524113 | Direct Life Insurance Carriers | | 524114 | Direct Health And Medical Insurance Carriers | | 524126 | Direct Property And Casualty Insurance Carriers | | 524127 | Direct Title Insurance Carriers | | 524128 | Other Direct Insurance Except Life Health And Medical Carriers | | 524130 | Reinsurance Carriers | | 524210 | Insurance Agencies And Brokerages | | 524291 | Claims Adjusting | | 524292 | Pharmacy Benefit Management And Other Third Party Administration | | 524298 | All Other Insurance Related Activities | | 525110 | Pension Funds | | 525120 | Health And Welfare Funds | | 525190 | Other Insurance Funds | | 525910 | Open End Investment Funds | | 525920 | Trusts Estates And Agency Accounts | | 525990 | Other Financial Vehicles | ## Government, Organizations | NAICS Code | Industry Description | | ---------- | -------------------------------------------------------------------------------- | | 813110 | Religious Organizations | | 813211 | Grantmaking Foundations | | 813212 | Voluntary Health Organizations | | 813219 | Other Grantmaking And Giving Services | | 813311 | Human Rights Organizations | | 813312 | Environment Conservation And Wildlife Organizations | | 813319 | Other Social Advocacy Organizations | | 813410 | Civic And Social Organizations | | 813910 | Business Associations | | 813920 | Professional Organizations | | 813930 | Labor Unions And Similar Labor Organizations | | 813940 | Political Organizations | | 813990 | Other Similar Organizations | | 921110 | Executive Offices | | 921120 | Legislative Bodies | | 921130 | Public Finance Activities | | 921140 | Executive And Legislative Offices Combined | | 921150 | American Indian And Alaska Native Tribal Governments | | 921190 | Other General Government Support | | 922110 | Courts | | 922120 | Police Protection | | 922130 | Legal Counsel And Prosecution | | 922140 | Correctional Institutions | | 922150 | Parole Offices And Probation Offices | | 922160 | Fire Protection | | 922190 | Other Justice Public Order And Safety Activities | | 923110 | Administration Of Education Programs | | 923120 | Administration Of Public Health Programs | | 923130 | Administration Of Human Resource Programs | | 923140 | Administration Of Veterans Affairs | | 924110 | Administration Of Air And Water Resource And Solid Waste Management Programs | | 924120 | Administration Of Conservation Programs | | 925110 | Administration Of Housing Programs | | 925120 | Administration Of Urban Planning And Community And Rural Development | | 926110 | Administration Of General Economic Programs | | 926120 | Regulation And Administration Of Transportation Programs | | 926130 | Regulation And Administration Of Communications Electric Gas And Other Utilities | | 926140 | Regulation Of Agricultural Marketing And Commodities | | 926150 | Regulation Licensing And Inspection Of Miscellaneous Commercial Sectors | | 927110 | Space Research And Technology | | 928110 | National Security | | 928120 | International Affairs | ## Holding Companies & Conglomerates | NAICS Code | Industry Description | | ---------- | -------------------------------------------------- | | 551111 | Offices Of Bank Holding Companies | | 551112 | Offices Of Other Holding Companies | | 551114 | Corporate Subsidiary And Regional Managing Offices | ## Hospitality, Recreation & Tourism | NAICS Code | Industry Description | | ---------- | ------------------------------------------------------------------------- | | 561510 | Travel Agencies | | 561520 | Tour Operators | | 561591 | Convention And Visitors Bureaus | | 561599 | All Other Travel Arrangement And Reservation Services | | 711110 | Theater Companies And Dinner Theaters | | 711120 | Dance Companies | | 711130 | Musical Groups And Artists | | 711190 | Other Performing Arts Companies | | 711211 | Sports Teams And Clubs | | 711212 | Racetracks | | 711219 | Other Spectator Sports | | 711310 | Promoters Of Performing Arts Sports And Similar Events With Facilities | | 711320 | Promoters Of Performing Arts Sports And Similar Events Without Facilities | | 712110 | Museums | | 712120 | Historical Sites | | 712130 | Zoos And Botanical Gardens | | 712190 | Nature Parks And Other Similar Institutions | | 713110 | Amusement And Theme Parks | | 713120 | Amusement Arcades | | 713210 | Casinos Except Casino Hotels | | 713290 | Other Gambling Industries | | 713910 | Golf Courses And Country Clubs | | 713920 | Skiing Facilities | | 713930 | Marinas | | 713940 | Fitness And Recreational Sports Centers | | 713950 | Bowling Centers | | 713990 | All Other Amusement And Recreation Industries | | 721110 | Hotels Except Casino Hotels And Motels | | 721120 | Casino Hotels | | 721191 | Bed And Breakfast Inns | | 721199 | All Other Traveler Accommodation | | 721211 | RV Recreational Vehicle Parks And Campgrounds | | 721214 | Recreational And Vacation Camps Except Campgrounds | | 721310 | Rooming And Boarding Houses Dormitories And Workers Camps | | 722310 | Food Service Contractors | | 722320 | Caterers | | 722330 | Mobile Food Services | | 722410 | Drinking Places Alcoholic Beverages | | 722511 | Full Service Restaurants | | 722513 | Limited Service Restaurants | | 722514 | Cafeterias Grill Buffets And Buffets | | 722515 | Snack And Nonalcoholic Beverage Bars | ## Hospitals, Clinics & Healthcare Services | NAICS Code | Industry Description | | ---------- | ----------------------------------------------------------------------- | | 541940 | Veterinary Services | | 621111 | Offices Of Physicians Except Mental Health Specialists | | 621112 | Offices Of Physicians Mental Health Specialists | | 621210 | Offices Of Dentists | | 621310 | Offices Of Chiropractors | | 621320 | Offices Of Optometrists | | 621330 | Offices Of Mental Health Practitioners Except Physicians | | 621340 | Offices Of Physical Occupational And Speech Therapists And Audiologists | | 621391 | Offices Of Podiatrists | | 621399 | Offices Of All Other Miscellaneous Health Practitioners | | 621410 | Family Planning Centers | | 621420 | Outpatient Mental Health And Substance Abuse Centers | | 621491 | HMO Medical Centers | | 621492 | Kidney Dialysis Centers | | 621493 | Freestanding Ambulatory Surgical And Emergency Centers | | 621498 | All Other Outpatient Care Centers | | 621511 | Medical Laboratories | | 621512 | Diagnostic Imaging Centers | | 621610 | Home Health Care Services | | 621910 | Ambulance Services | | 621991 | Blood And Organ Banks | | 621999 | All Other Miscellaneous Ambulatory Health Care Services | | 622110 | General Medical And Surgical Hospitals | | 622210 | Psychiatric And Substance Abuse Hospitals | | 622310 | Specialty Except Psychiatric And Substance Abuse Hospitals | | 623110 | Nursing Care Facilities Skilled Nursing Facilities | | 623210 | Residential Intellectual And Developmental Disability Facilities | | 623220 | Residential Mental Health And Substance Abuse Facilities | | 623311 | Continuing Care Retirement Communities | | 623312 | Assisted Living Facilities For The Elderly | | 623990 | Other Residential Care Facilities | ## Law Firms & Legal Services | NAICS Code | Industry Description | | ---------- | ------------------------------------- | | 541110 | Offices Of Lawyers | | 541191 | Title Abstract And Settlement Offices | | 541199 | All Other Legal Services | ## Manufacturing | NAICS Code | Industry Description | | ---------- | ------------------------------------------------------------------------------------------------------------------- | | 311111 | Dog And Cat Food Manufacturing | | 311119 | Other Animal Food Manufacturing | | 311211 | Flour Milling | | 311212 | Rice Milling | | 311213 | Malt Manufacturing | | 311221 | Wet Corn Milling And Starch Manufacturing | | 311224 | Soybean And Other Oilseed Processing | | 311225 | Fats And Oils Refining And Blending | | 311230 | Breakfast Cereal Manufacturing | | 311313 | Beet Sugar Manufacturing | | 311314 | Cane Sugar Manufacturing | | 311340 | Nonchocolate Confectionery Manufacturing | | 311351 | Chocolate And Confectionery Manufacturing From Cacao Beans | | 311352 | Confectionery Manufacturing From Purchased Chocolate | | 311411 | Frozen Fruit Juice And Vegetable Manufacturing | | 311412 | Frozen Specialty Food Manufacturing | | 311421 | Fruit And Vegetable Canning | | 311422 | Specialty Canning | | 311423 | Dried And Dehydrated Food Manufacturing | | 311511 | Fluid Milk Manufacturing | | 311512 | Creamery Butter Manufacturing | | 311513 | Cheese Manufacturing | | 311514 | Dry Condensed And Evaporated Dairy Product Manufacturing | | 311520 | Ice Cream And Frozen Dessert Manufacturing | | 311611 | Animal Except Poultry Slaughtering | | 311612 | Meat Processed From Carcasses | | 311613 | Rendering And Meat Byproduct Processing | | 311615 | Poultry Processing | | 311710 | Seafood Product Preparation And Packaging | | 311811 | Retail Bakeries | | 311812 | Commercial Bakeries | | 311813 | Frozen Cakes Pies And Other Pastries Manufacturing | | 311821 | Cookie And Cracker Manufacturing | | 311824 | Dry Pasta Dough And Flour Mixes Manufacturing From Purchased Flour | | 311830 | Tortilla Manufacturing | | 311911 | Roasted Nuts And Peanut Butter Manufacturing | | 311919 | Other Snack Food Manufacturing | | 311920 | Coffee And Tea Manufacturing | | 311930 | Flavoring Syrup And Concentrate Manufacturing | | 311941 | Mayonnaise Dressing And Other Prepared Sauce Manufacturing | | 311942 | Spice And Extract Manufacturing | | 311991 | Perishable Prepared Food Manufacturing | | 311999 | All Other Miscellaneous Food Manufacturing | | 312111 | Soft Drink Manufacturing | | 312112 | Bottled Water Manufacturing | | 312113 | Ice Manufacturing | | 312120 | Breweries | | 312130 | Wineries | | 312140 | Distilleries | | 312230 | Tobacco Manufacturing | | 313110 | Fiber Yarn And Thread Mills | | 313210 | Broadwoven Fabric Mills | | 313220 | Narrow Fabric Mills And Schiffli Machine Embroidery | | 313230 | Nonwoven Fabric Mills | | 313240 | Knit Fabric Mills | | 313310 | Textile And Fabric Finishing Mills | | 313320 | Fabric Coating Mills | | 314110 | Carpet And Rug Mills | | 314120 | Curtain And Linen Mills | | 314910 | Textile Bag And Canvas Mills | | 314994 | Rope Cordage Twine Tire Cord And Tire Fabric Mills | | 314999 | All Other Miscellaneous Textile Product Mills | | 315120 | Apparel Knitting Mills | | 315210 | Cut And Sew Apparel Contractors | | 315250 | Cut And Sew Apparel Manufacturing Except Contractors | | 315990 | Apparel Accessories And Other Apparel Manufacturing | | 316110 | Leather And Hide Tanning And Finishing | | 316210 | Footwear Manufacturing | | 316990 | Other Leather And Allied Product Manufacturing | | 321113 | Sawmills | | 321114 | Wood Preservation | | 321211 | Hardwood Veneer And Plywood Manufacturing | | 321212 | Softwood Veneer And Plywood Manufacturing | | 321215 | Engineered Wood Member Manufacturing | | 321219 | Reconstituted Wood Product Manufacturing | | 321911 | Wood Window And Door Manufacturing | | 321912 | Cut Stock Resawing Lumber And Planing | | 321918 | Other Millwork Including Flooring | | 321920 | Wood Container And Pallet Manufacturing | | 321991 | Manufactured Home Mobile Home Manufacturing | | 321992 | Prefabricated Wood Building Manufacturing | | 321999 | All Other Miscellaneous Wood Product Manufacturing | | 322110 | Pulp Mills | | 322120 | Paper Mills | | 322130 | Paperboard Mills | | 322211 | Corrugated And Solid Fiber Box Manufacturing | | 322212 | Folding Paperboard Box Manufacturing | | 322219 | Other Paperboard Container Manufacturing | | 322220 | Paper Bag And Coated And Treated Paper Manufacturing | | 322230 | Stationery Product Manufacturing | | 322291 | Sanitary Paper Product Manufacturing | | 322299 | All Other Converted Paper Product Manufacturing | | 323111 | Commercial Printing Except Screen And Books | | 323113 | Commercial Screen Printing | | 323117 | Books Printing | | 323120 | Support Activities For Printing | | 324110 | Petroleum Refineries | | 324121 | Asphalt Paving Mixture And Block Manufacturing | | 324122 | Asphalt Shingle And Coating Materials Manufacturing | | 324191 | Petroleum Lubricating Oil And Grease Manufacturing | | 324199 | All Other Petroleum And Coal Products Manufacturing | | 325110 | Petrochemical Manufacturing | | 325120 | Industrial Gas Manufacturing | | 325130 | Synthetic Dye And Pigment Manufacturing | | 325180 | Other Basic Inorganic Chemical Manufacturing | | 325193 | Ethyl Alcohol Manufacturing | | 325194 | Cyclic Crude Intermediate And Gum And Wood Chemical Manufacturing | | 325199 | All Other Basic Organic Chemical Manufacturing | | 325211 | Plastics Material And Resin Manufacturing | | 325212 | Synthetic Rubber Manufacturing | | 325220 | Artificial And Synthetic Fibers And Filaments Manufacturing | | 325311 | Nitrogenous Fertilizer Manufacturing | | 325312 | Phosphatic Fertilizer Manufacturing | | 325314 | Fertilizer Mixing Only Manufacturing | | 325315 | Compost Manufacturing | | 325320 | Pesticide And Other Agricultural Chemical Manufacturing | | 325411 | Medicinal And Botanical Manufacturing | | 325412 | Pharmaceutical Preparation Manufacturing | | 325413 | In Vitro Diagnostic Substance Manufacturing | | 325414 | Biological Product Except Diagnostic Manufacturing | | 325510 | Paint And Coating Manufacturing | | 325520 | Adhesive Manufacturing | | 325611 | Soap And Other Detergent Manufacturing | | 325612 | Polish And Other Sanitation Good Manufacturing | | 325613 | Surface Active Agent Manufacturing | | 325620 | Toilet Preparation Manufacturing | | 325910 | Printing Ink Manufacturing | | 325920 | Explosives Manufacturing | | 325991 | Custom Compounding Of Purchased Resins | | 325992 | Photographic Film Paper Plate Chemical And Copy Toner Manufacturing | | 325998 | All Other Miscellaneous Chemical Product And Preparation Manufacturing | | 326111 | Plastics Bag And Pouch Manufacturing | | 326112 | Plastics Packaging Film And Sheet Including Laminated Manufacturing | | 326113 | Unlaminated Plastics Film And Sheet Except Packaging Manufacturing | | 326121 | Unlaminated Plastics Profile Shape Manufacturing | | 326122 | Plastics Pipe And Pipe Fitting Manufacturing | | 326130 | Laminated Plastics Plate Sheet Except Packaging And Shape Manufacturing | | 326140 | Polystyrene Foam Product Manufacturing | | 326150 | Urethane And Other Foam Product Except Polystyrene Manufacturing | | 326160 | Plastics Bottle Manufacturing | | 326191 | Plastics Plumbing Fixture Manufacturing | | 326199 | All Other Plastics Product Manufacturing | | 326211 | Tire Manufacturing Except Retreading | | 326212 | Tire Retreading | | 326220 | Rubber And Plastics Hoses And Belting Manufacturing | | 326291 | Rubber Product Manufacturing For Mechanical Use | | 326299 | All Other Rubber Product Manufacturing | | 327110 | Pottery Ceramics And Plumbing Fixture Manufacturing | | 327120 | Clay Building Material And Refractories Manufacturing | | 327211 | Flat Glass Manufacturing | | 327212 | Other Pressed And Blown Glass And Glassware Manufacturing | | 327213 | Glass Container Manufacturing | | 327215 | Glass Product Manufacturing Made Of Purchased Glass | | 327310 | Cement Manufacturing | | 327320 | Ready Mix Concrete Manufacturing | | 327331 | Concrete Block And Brick Manufacturing | | 327332 | Concrete Pipe Manufacturing | | 327390 | Other Concrete Product Manufacturing | | 327410 | Lime Manufacturing | | 327420 | Gypsum Product Manufacturing | | 327910 | Abrasive Product Manufacturing | | 327991 | Cut Stone And Stone Product Manufacturing | | 327992 | Ground Or Treated Mineral And Earth Manufacturing | | 327993 | Mineral Wool Manufacturing | | 327999 | All Other Miscellaneous Nonmetallic Mineral Product Manufacturing | | 331110 | Iron And Steel Mills And Ferroalloy Manufacturing | | 331210 | Iron And Steel Pipe And Tube Manufacturing From Purchased Steel | | 331221 | Rolled Steel Shape Manufacturing | | 331222 | Steel Wire Drawing | | 331313 | Alumina Refining And Primary Aluminum Production | | 331314 | Secondary Smelting And Alloying Of Aluminum | | 331315 | Aluminum Sheet Plate And Foil Manufacturing | | 331318 | Other Aluminum Rolling Drawing And Extruding | | 331410 | Nonferrous Metal Except Aluminum Smelting And Refining | | 331420 | Copper Rolling Drawing Extruding And Alloying | | 331491 | Nonferrous Metal Except Copper And Aluminum Rolling Drawing And Extruding | | 331492 | Secondary Smelting Refining And Alloying Of Nonferrous Metal Except Copper And Aluminum | | 331511 | Iron Foundries | | 331512 | Steel Investment Foundries | | 331513 | Steel Foundries Except Investment | | 331523 | Nonferrous Metal Die Casting Foundries | | 331524 | Aluminum Foundries Except Die Casting | | 331529 | Other Nonferrous Metal Foundries Except Die Casting | | 332111 | Iron And Steel Forging | | 332112 | Nonferrous Forging | | 332114 | Custom Roll Forming | | 332117 | Powder Metallurgy Part Manufacturing | | 332119 | Metal Crown Closure And Other Metal Stamping Except Automotive | | 332215 | Metal Kitchen Cookware Utensil Cutlery And Flatware Except Precious Manufacturing | | 332216 | Saw Blade And Handtool Manufacturing | | 332311 | Prefabricated Metal Building And Component Manufacturing | | 332312 | Fabricated Structural Metal Manufacturing | | 332313 | Plate Work Manufacturing | | 332321 | Metal Window And Door Manufacturing | | 332322 | Sheet Metal Work Manufacturing | | 332323 | Ornamental And Architectural Metal Work Manufacturing | | 332410 | Power Boiler And Heat Exchanger Manufacturing | | 332420 | Metal Tank Heavy Gauge Manufacturing | | 332431 | Metal Can Manufacturing | | 332439 | Other Metal Container Manufacturing | | 332510 | Hardware Manufacturing | | 332613 | Spring Manufacturing | | 332618 | Other Fabricated Wire Product Manufacturing | | 332710 | Machine Shops | | 332721 | Precision Turned Product Manufacturing | | 332722 | Bolt Nut Screw Rivet And Washer Manufacturing | | 332811 | Metal Heat Treating | | 332812 | Metal Coating Engraving Except Jewelry And Silverware And Allied Services To Manufacturers | | 332813 | Electroplating Plating Polishing Anodizing And Coloring | | 332911 | Industrial Valve Manufacturing | | 332912 | Fluid Power Valve And Hose Fitting Manufacturing | | 332913 | Plumbing Fixture Fitting And Trim Manufacturing | | 332919 | Other Metal Valve And Pipe Fitting Manufacturing | | 332991 | Ball And Roller Bearing Manufacturing | | 332992 | Small Arms Ammunition Manufacturing | | 332993 | Ammunition Except Small Arms Manufacturing | | 332994 | Small Arms Ordnance And Ordnance Accessories Manufacturing | | 332996 | Fabricated Pipe And Pipe Fitting Manufacturing | | 332999 | All Other Miscellaneous Fabricated Metal Product Manufacturing | | 333111 | Farm Machinery And Equipment Manufacturing | | 333112 | Lawn And Garden Tractor And Home Lawn And Garden Equipment Manufacturing | | 333120 | Construction Machinery Manufacturing | | 333131 | Mining Machinery And Equipment Manufacturing | | 333132 | Oil And Gas Field Machinery And Equipment Manufacturing | | 333241 | Food Product Machinery Manufacturing | | 333242 | Semiconductor Machinery Manufacturing | | 333243 | Sawmill Woodworking And Paper Machinery Manufacturing | | 333248 | All Other Industrial Machinery Manufacturing | | 333310 | Commercial And Service Industry Machinery Manufacturing | | 333413 | Industrial And Commercial Fan And Blower And Air Purification Equipment Manufacturing | | 333414 | Heating Equipment Except Warm Air Furnaces Manufacturing | | 333415 | Air Conditioning And Warm Air Heating Equipment And Commercial And Industrial Refrigeration Equipment Manufacturing | | 333511 | Industrial Mold Manufacturing | | 333514 | Special Die And Tool Die Set Jig And Fixture Manufacturing | | 333515 | Cutting Tool And Machine Tool Accessory Manufacturing | | 333517 | Machine Tool Manufacturing | | 333519 | Rolling Mill And Other Metalworking Machinery Manufacturing | | 333611 | Turbine And Turbine Generator Set Units Manufacturing | | 333612 | Speed Changer Industrial High Speed Drive And Gear Manufacturing | | 333613 | Mechanical Power Transmission Equipment Manufacturing | | 333618 | Other Engine Equipment Manufacturing | | 333912 | Air And Gas Compressor Manufacturing | | 333914 | Measuring Dispensing And Other Pumping Equipment Manufacturing | | 333921 | Elevator And Moving Stairway Manufacturing | | 333922 | Conveyor And Conveying Equipment Manufacturing | | 333923 | Overhead Traveling Crane Hoist And Monorail System Manufacturing | | 333924 | Industrial Truck Tractor Trailer And Stacker Machinery Manufacturing | | 333991 | Power Driven Handtool Manufacturing | | 333992 | Welding And Soldering Equipment Manufacturing | | 333993 | Packaging Machinery Manufacturing | | 333994 | Industrial Process Furnace And Oven Manufacturing | | 333995 | Fluid Power Cylinder And Actuator Manufacturing | | 333996 | Fluid Power Pump And Motor Manufacturing | | 333998 | All Other Miscellaneous General Purpose Machinery Manufacturing | | 334111 | Electronic Computer Manufacturing | | 334112 | Computer Storage Device Manufacturing | | 334118 | Computer Terminal And Other Computer Peripheral Equipment Manufacturing | | 334210 | Telephone Apparatus Manufacturing | | 334220 | Radio And Television Broadcasting And Wireless Communications Equipment Manufacturing | | 334290 | Other Communications Equipment Manufacturing | | 334310 | Audio And Video Equipment Manufacturing | | 334412 | Bare Printed Circuit Board Manufacturing | | 334413 | Semiconductor And Related Device Manufacturing | | 334416 | Capacitor Resistor Coil Transformer And Other Inductor Manufacturing | | 334417 | Electronic Connector Manufacturing | | 334418 | Printed Circuit Assembly Electronic Assembly Manufacturing | | 334419 | Other Electronic Component Manufacturing | | 334510 | Electromedical And Electrotherapeutic Apparatus Manufacturing | | 334511 | Search Detection Navigation Guidance Aeronautical And Nautical System And Instrument Manufacturing | | 334512 | Automatic Environmental Control Manufacturing | | 334513 | Instruments For Measuring Displaying And Controlling Industrial Process Variables | | 334514 | Totalizing Fluid Meter And Counting Device Manufacturing | | 334515 | Instrument Manufacturing For Measuring And Testing Electricity And Electrical Signals | | 334516 | Analytical Laboratory Instrument Manufacturing | | 334517 | Irradiation Apparatus Manufacturing | | 334519 | Other Measuring And Controlling Device Manufacturing | | 334610 | Manufacturing And Reproducing Magnetic And Optical Media | | 335131 | Residential Electric Lighting Fixture Manufacturing | | 335132 | Commercial Industrial And Institutional Electric Lighting Fixture Manufacturing | | 335139 | Electric Lamp Bulb And Other Lighting Equipment Manufacturing | | 335210 | Small Electrical Appliance Manufacturing | | 335220 | Major Household Appliance Manufacturing | | 335311 | Power Distribution And Specialty Transformer Manufacturing | | 335312 | Motor And Generator Manufacturing | | 335313 | Switchgear And Switchboard Apparatus Manufacturing | | 335314 | Relay And Industrial Control Manufacturing | | 335910 | Battery Manufacturing | | 335921 | Fiber Optic Cable Manufacturing | | 335929 | Other Communication And Energy Wire Manufacturing | | 335931 | Current Carrying Wiring Device Manufacturing | | 335932 | Noncurrent Carrying Wiring Device Manufacturing | | 335991 | Carbon And Graphite Product Manufacturing | | 335999 | All Other Miscellaneous Electrical Equipment And Component Manufacturing | | 336110 | Automobile And Light Duty Motor Vehicle Manufacturing | | 336120 | Heavy Duty Truck Manufacturing | | 336211 | Motor Vehicle Body Manufacturing | | 336212 | Truck Trailer Manufacturing | | 336213 | Motor Home Manufacturing | | 336214 | Travel Trailer And Camper Manufacturing | | 336310 | Motor Vehicle Gasoline Engine And Engine Parts Manufacturing | | 336320 | Motor Vehicle Electrical And Electronic Equipment Manufacturing | | 336330 | Motor Vehicle Steering And Suspension Components Except Spring Manufacturing | | 336340 | Motor Vehicle Brake System Manufacturing | | 336350 | Motor Vehicle Transmission And Power Train Parts Manufacturing | | 336360 | Motor Vehicle Seating And Interior Trim Manufacturing | | 336370 | Motor Vehicle Metal Stamping | | 336390 | Other Motor Vehicle Parts Manufacturing | | 336411 | Aircraft Manufacturing | | 336412 | Aircraft Engine And Engine Parts Manufacturing | | 336413 | Other Aircraft Parts And Auxiliary Equipment Manufacturing | | 336414 | Guided Missile And Space Vehicle Manufacturing | | 336415 | Guided Missile And Space Vehicle Propulsion Unit And Parts Manufacturing | | 336419 | Other Guided Missile And Space Vehicle Parts | | 336510 | Railroad Rolling Stock Manufacturing | | 336611 | Ship Building And Repairing | | 336612 | Boat Building | | 336991 | Motorcycle Bicycle And Parts Manufacturing | | 336992 | Military Armored Vehicle Tank And Tank Component Manufacturing | | 336999 | All Other Transportation Equipment Manufacturing | | 337110 | Wood Kitchen Cabinet And Countertop Manufacturing | | 337121 | Upholstered Household Furniture Manufacturing | | 337122 | Nonupholstered Wood Household Furniture Manufacturing | | 337126 | Household Furniture Except Wood And Upholstered Manufacturing | | 337127 | Institutional Furniture Manufacturing | | 337211 | Wood Office Furniture Manufacturing | | 337212 | Custom Architectural Woodwork And Millwork Manufacturing | | 337214 | Office Furniture Except Wood Manufacturing | | 337215 | Showcase Partition Shelving And Locker Manufacturing | | 337910 | Mattress Manufacturing | | 337920 | Blind And Shade Manufacturing | | 339112 | Surgical And Medical Instrument Manufacturing | | 339113 | Surgical Appliance And Supplies Manufacturing | | 339114 | Dental Equipment And Supplies Manufacturing | | 339115 | Ophthalmic Goods Manufacturing | | 339116 | Dental Laboratories | | 339910 | Jewelry And Silverware Manufacturing | | 339920 | Sporting And Athletic Goods Manufacturing | | 339930 | Doll Toy And Game Manufacturing | | 339940 | Office Supplies Except Paper Manufacturing | | 339950 | Sign Manufacturing | | 339991 | Gasket Packing And Sealing Device Manufacturing | | 339992 | Musical Instrument Manufacturing | | 339993 | Fastener Button Needle And Pin Manufacturing | | 339994 | Broom Brush And Mop Manufacturing | | 339995 | Burial Casket Manufacturing | | 339999 | All Other Miscellaneous Manufacturing | ## Media, Arts & Entertainment | NAICS Code | Industry Description | | ---------- | ----------------------------------------------------- | | 512110 | Motion Picture And Video Production | | 512120 | Motion Picture And Video Distribution | | 512131 | Motion Picture Theaters Except Drive Ins | | 512132 | Drive In Motion Picture Theaters | | 512191 | Teleproduction And Other Postproduction Services | | 512199 | Other Motion Picture And Video Industries | | 512230 | Music Publishers | | 512240 | Sound Recording Studios | | 512250 | Record Production And Distribution | | 512290 | Other Sound Recording Industries | | 513110 | Newspaper Publishers | | 513120 | Periodical Publishers | | 513130 | Book Publishers | | 513140 | Directory And Mailing List Publishers | | 513191 | Greeting Card Publishers | | 513199 | All Other Publishers | | 513210 | Software Publishers | | 515210 | Cable And Other Subscription Programming | | 516110 | Radio Broadcasting Stations | | 516120 | Television Broadcasting Stations | | 516210 | Media Networks And Streaming | | 519130 | Digital Goods Marketplace And Web Portal | | 519210 | Libraries And Archives | | 519290 | Web Portals And Other Info Services | | 711410 | Agents And Managers For Artists Athletes Entertainers | | 711510 | Independent Artists Writers And Performers | ## Real Estate | NAICS Code | Industry Description | | ---------- | --------------------------------------------------------- | | 531110 | Lessors Of Residential Buildings And Dwellings | | 531120 | Lessors Of Nonresidential Buildings Except Miniwarehouses | | 531130 | Lessors Of Miniwarehouses And Self Storage Units | | 531190 | Lessors Of Other Real Estate Property | | 531210 | Offices Of Real Estate Agents And Brokers | | 531311 | Residential Property Managers | | 531312 | Nonresidential Property Managers | | 531320 | Offices Of Real Estate Appraisers | | 531390 | Other Activities Related To Real Estate | ## Retail Trade | NAICS Code | Industry Description | | ---------- | ----------------------------------------------------------------- | | 441110 | New Car Dealers | | 441120 | Used Car Dealers | | 441210 | Recreational Vehicle Dealers | | 441222 | Boat Dealers | | 441227 | Motorcycle ATV And All Other Motor Vehicle Dealers | | 441330 | Automotive Parts And Accessories Retailers | | 441340 | Tire Dealers | | 444110 | Home Centers | | 444120 | Paint And Wallpaper Retailers | | 444140 | Hardware Retailers | | 444180 | Other Building Material Dealers | | 444230 | Outdoor Power Equipment Retailers | | 444240 | Nursery Garden Center And Farm Supply Retailers | | 445110 | Supermarkets And Other Grocery Retailers | | 445131 | Convenience Retailers | | 445132 | Vending Machine Operators | | 445230 | Fruit And Vegetable Retailers | | 445240 | Meat Retailers | | 445250 | Fish And Seafood Retailers | | 445291 | Baked Goods Retailers | | 445292 | Confectionery And Nut Retailers | | 445298 | All Other Specialty Food Retailers | | 445320 | Beer Wine And Liquor Retailers | | 449110 | Furniture Retailers | | 449121 | Floor Covering Retailers | | 449122 | Window Treatment Retailers | | 449129 | All Other Home Furnishings Retailers | | 449210 | Electronics And Appliance Retailers | | 454110 | Peer To Peer Online Marketplace | | 454113 | Mail Order Houses | | 454390 | Other Direct Selling Establishments | | 455110 | Department Stores | | 455211 | Warehouse Clubs And Supercenters | | 455219 | All Other General Merchandise Retailers | | 456110 | Pharmacies And Drug Retailers | | 456120 | Cosmetics, Beauty Supplies And Perfume Retailers | | 446120 | Cosmetics, Beauty Supplies, and Perfume Stores | | 456130 | Optical Goods Retailers | | 456191 | Food Health Supplement Retailers | | 456199 | All Other Health And Personal Care Retailers | | 457110 | Gasoline Stations With Convenience Stores | | 457120 | Other Gasoline Stations | | 457210 | Fuel Dealers | | 458110 | Clothing And Clothing Accessories Retailers | | 458210 | Shoe Retailers | | 458310 | Jewelry Retailers | | 458320 | Luggage And Leather Goods Retailers | | 459110 | Sporting Goods Retailers | | 459120 | Hobby Toy And Game Retailers | | 459130 | Sewing Needlework And Piece Goods Retailers | | 459140 | Musical Instrument And Supplies Retailers | | 459210 | Book Retailers And News Dealers | | 459310 | Florists | | 459410 | Office Supplies And Stationery Retailers | | 459420 | Gift Novelty And Souvenir Retailers | | 459510 | Used Merchandise Retailers | | 459910 | Pet And Pet Supplies Retailers | | 459920 | Art Dealers | | 459930 | Manufactured Mobile Home Dealers | | 459991 | Tobacco Electronic Cigarette And Other Smoking Supplies Retailers | | 459999 | All Other Miscellaneous Retailers | ## Software, IT and Telecommunications | NAICS Code | Industry Description | | ---------- | ----------------------------------------------------------------------------------- | | 511210 | Software Publishing | | 517111 | Wired Telecommunications Carriers | | 517112 | Wireless Telecommunications Carriers Except Satellite | | 517121 | Telecommunications Resellers | | 517410 | Satellite Telecommunications | | 517810 | All Other Telecommunications | | 518210 | Computing Infrastructure Providers Data Processing Web Hosting And Related Services | | 541511 | Custom Computer Programming Services | | 541512 | Computer Systems Design Services | | 541519 | Other Computer Related Services | ## Transportation, Warehousing, Wholesale | NAICS Code | Industry Description | | ---------- | -------------------------------------------------------------------------------------- | | 423110 | Automobile And Other Motor Vehicle Merchant Wholesalers | | 423120 | Motor Vehicle Supplies And New Parts Merchant Wholesalers | | 423130 | Tire And Tube Merchant Wholesalers | | 423140 | Motor Vehicle Parts Used Merchant Wholesalers | | 423210 | Furniture Merchant Wholesalers | | 423220 | Home Furnishing Merchant Wholesalers | | 423310 | Lumber Plywood Millwork And Wood Panel Merchant Wholesalers | | 423320 | Brick Stone And Related Construction Material Merchant Wholesalers | | 423330 | Roofing Siding And Insulation Material Merchant Wholesalers | | 423390 | Other Construction Material Merchant Wholesalers | | 423410 | Photographic Equipment And Supplies Merchant Wholesalers | | 423420 | Office Equipment Merchant Wholesalers | | 423430 | Computer And Computer Peripheral Equipment And Software Merchant Wholesalers | | 423440 | Other Commercial Equipment Merchant Wholesalers | | 423450 | Medical Dental And Hospital Equipment And Supplies Merchant Wholesalers | | 423460 | Ophthalmic Goods Merchant Wholesalers | | 423490 | Other Professional Equipment And Supplies Merchant Wholesalers | | 423510 | Metal Service Centers And Other Metal Merchant Wholesalers | | 423520 | Coal And Other Mineral And Ore Merchant Wholesalers | | 423610 | Electrical Apparatus And Equipment Wiring Supplies Merchant Wholesalers | | 423620 | Household Appliances Electric Housewares And Consumer Electronics Merchant Wholesalers | | 423690 | Other Electronic Parts And Equipment Merchant Wholesalers | | 423710 | Hardware Merchant Wholesalers | | 423720 | Plumbing And Heating Equipment And Supplies Merchant Wholesalers | | 423730 | Warm Air Heating And Air Conditioning Equipment And Supplies Merchant Wholesalers | | 423740 | Refrigeration Equipment And Supplies Merchant Wholesalers | | 423810 | Construction And Mining Machinery And Equipment Merchant Wholesalers | | 423820 | Farm And Garden Machinery And Equipment Merchant Wholesalers | | 423830 | Industrial Machinery And Equipment Merchant Wholesalers | | 423840 | Industrial Supplies Merchant Wholesalers | | 423850 | Service Establishment Equipment And Supplies Merchant Wholesalers | | 423860 | Transportation Equipment And Supplies Merchant Wholesalers | | 423910 | Sporting And Recreational Goods And Supplies Merchant Wholesalers | | 423920 | Toy And Hobby Goods And Supplies Merchant Wholesalers | | 423930 | Recyclable Material Merchant Wholesalers | | 423940 | Jewelry Watch Precious Stone And Precious Metal Merchant Wholesalers | | 423990 | Other Miscellaneous Durable Goods Merchant Wholesalers | | 424110 | Printing And Writing Paper Merchant Wholesalers | | 424120 | Stationery And Office Supplies Merchant Wholesalers | | 424130 | Industrial And Personal Service Paper Merchant Wholesalers | | 424210 | Drugs Wholesalers | | 424310 | Piece Goods Notions And Other Dry Goods Merchant Wholesalers | | 424340 | Footwear Merchant Wholesalers | | 424350 | Clothing And Clothing Accessories Merchant Wholesalers | | 424410 | General Line Grocery Merchant Wholesalers | | 424420 | Packaged Frozen Food Merchant Wholesalers | | 424430 | Dairy Product Except Dried Or Canned Merchant Wholesalers | | 424440 | Poultry And Poultry Product Merchant Wholesalers | | 424450 | Confectionery Merchant Wholesalers | | 424460 | Fish And Seafood Merchant Wholesalers | | 424470 | Meat And Meat Product Merchant Wholesalers | | 424480 | Fresh Fruit And Vegetable Merchant Wholesalers | | 424490 | Other Grocery And Related Products Merchant Wholesalers | | 424510 | Grain And Field Bean Merchant Wholesalers | | 424520 | Livestock Merchant Wholesalers | | 424590 | Other Farm Product Raw Material Merchant Wholesalers | | 424610 | Plastics Materials And Basic Forms And Shapes Merchant Wholesalers | | 424690 | Other Chemical And Allied Products Merchant Wholesalers | | 424710 | Petroleum Bulk Stations And Terminals | | 424720 | Petroleum And Petroleum Products Merchant Wholesalers | | 424810 | Beer And Ale Merchant Wholesalers | | 424820 | Wine And Distilled Alcoholic Beverage Merchant Wholesalers | | 424910 | Farm Supplies Merchant Wholesalers | | 424920 | Book Periodical And Newspaper Merchant Wholesalers | | 424930 | Flower Nursery Stock And Florists Supplies Merchant Wholesalers | | 424940 | Tobacco Product And Electronic Cigarette Merchant Wholesalers | | 424950 | Paint Varnish And Supplies Merchant Wholesalers | | 424990 | Other Miscellaneous Nondurable Goods Merchant Wholesalers | | 425120 | Wholesale Trade Agents And Brokers | | 481111 | Scheduled Passenger Air Transportation | | 481112 | Scheduled Freight Air Transportation | | 481211 | Nonscheduled Chartered Passenger Air Transportation | | 481212 | Nonscheduled Chartered Freight Air Transportation | | 481219 | Other Nonscheduled Air Transportation | | 482111 | Line Haul Railroads | | 482112 | Short Line Railroads | | 483111 | Deep Sea Freight Transportation | | 483112 | Deep Sea Passenger Transportation | | 483113 | Coastal And Great Lakes Freight Transportation | | 483114 | Coastal And Great Lakes Passenger Transportation | | 483211 | Inland Water Freight Transportation | | 483212 | Inland Water Passenger Transportation | | 484110 | General Freight Trucking Local | | 484121 | General Freight Trucking Long Distance Truckload | | 484122 | General Freight Trucking Long Distance Less Than Truckload | | 484210 | Used Household And Office Goods Moving | | 484220 | Specialized Freight Except Used Goods Trucking Local | | 484230 | Specialized Freight Except Used Goods Trucking Long Distance | | 485111 | Mixed Mode Transit Systems | | 485112 | Commuter Rail Systems | | 485113 | Bus And Other Motor Vehicle Transit Systems | | 485119 | Other Urban Transit Systems | | 485210 | Interurban And Rural Bus Transportation | | 485310 | Taxi And Ridesharing Services | | 485320 | Limousine Service | | 485410 | School And Employee Bus Transportation | | 485510 | Charter Bus Industry | | 485991 | Special Needs Transportation | | 485999 | All Other Transit And Ground Passenger Transportation | | 486110 | Pipeline Transportation Of Crude Oil | | 486210 | Pipeline Transportation Of Natural Gas | | 486910 | Pipeline Transportation Of Refined Petroleum Products | | 486990 | All Other Pipeline Transportation | | 487110 | Scenic And Sightseeing Transportation Land | | 487210 | Scenic And Sightseeing Transportation Water | | 487990 | Scenic And Sightseeing Transportation Other | | 488111 | Air Traffic Control | | 488119 | Other Airport Operations | | 488190 | Other Support Activities For Air Transportation | | 488210 | Support Activities For Rail Transportation | | 488310 | Port And Harbor Operations | | 488320 | Marine Cargo Handling | | 488330 | Navigational Services To Shipping | | 488390 | Other Support Activities For Water Transportation | | 488410 | Motor Vehicle Towing | | 488490 | Other Support Activities For Road Transportation | | 488510 | Freight Transportation Arrangement | | 488991 | Packing And Crating | | 488999 | All Other Support Activities For Transportation | | 491110 | Postal Service | | 492110 | Couriers And Express Delivery Services | | 492210 | Local Messengers And Local Delivery | | 493110 | General Warehousing And Storage | | 493120 | Refrigerated Warehousing And Storage | | 493130 | Farm Product Warehousing And Storage | | 493190 | Other Warehousing And Storage | ## Other | NAICS Code | Industry Description | | ---------- | -------------------- | | 999999 | Unclassified | --- --- url: /docs/kb/virtual-accounts.md description: >- Documentation required for the Virtual Account evaluation, including source of funds and source of wealth supporting documents. --- ## Summary [Virtual Account](/docs/virtual-accounts) requests go through a separate evaluation with their own documentation requirements, in addition to standard [KYB verification](/docs/kb/kyb-documents). You must provide business profile details (industry, entity type, nature, purpose) plus source of funds and source of wealth, backed by supporting financial documents. ## Mandatory documentation | Item | What to provide | | --- | --- | | **Industry type** | Industry that best describes your business (e.g., Technology, E-Commerce Platforms, Software Development, Educational Services). | | **Entity type confirmation** | Your legal structure (e.g., Corporation, Company Limited by Shares, LLC). | | **Nature of business** | Brief high-level description of your products or services (up to 250 characters). | | **Purpose of the account** | How you plan to use your BlindPay account (e.g., payments, settlements, treasury management, operational flows). | | **Source of Wealth** | How the business or its owners originally accumulated their overall wealth (e.g., business earnings, prior ventures, long-term investments). | | **Source of Funds** | How the funds you'll use on BlindPay are generated (e.g., operating revenue, customer payments, investment capital). | ## Source of funds supporting documents The Source of Funds document must demonstrate financial capacity and the origin of funds. It must be issued in the account holder's name and be consistent with the business description provided during onboarding, clearly reflecting transactions and financial activity that explain the nature and origin of funds expected to flow through the account. ### Company (KYB) * Audited financial statements * Unaudited/management accounts (for recently incorporated companies) * Bank statements for the last 3 months showing activity related to the company's operations * Tax returns or annual tax filings * Contracts or invoices demonstrating business activity ### Individual (KYC) * Last year of personal tax returns * Last 3 months of bank statements * Employment contract (if salaried) For full documentation requirements and accepted documents, see the [Source of Funds & Source of Wealth](/docs/kb/source-of-funds) guide. ## Related * [Virtual Accounts](/docs/virtual-accounts) · [KYB Documents](/docs/kb/kyb-documents) * [Source of Funds & Source of Wealth](/docs/kb/source-of-funds) --- --- url: /docs/kb/prohibited-activities.md description: >- High-risk and prohibited business activities at BlindPay, and the disclosure obligations required during onboarding and ongoing monitoring. --- ## Summary All customers must fully disclose their business activities to BlindPay during onboarding and ongoing monitoring. High-risk activities (such as money services, VASPs, or licensed gambling) are allowed with disclosure, Enhanced Due Diligence, and ongoing monitoring. Prohibited activities are never supported under any circumstances. Failure to disclose relevant activities may result in onboarding rejection, account suspension, or termination. ## High-Risk Business Activities **Allowed with disclosure, risk assessment, and enhanced controls.** These activities are considered high risk. Being in one of these categories does not automatically disqualify you, but requires enhanced due diligence and ongoing monitoring. You must disclose these activities during onboarding. This list is not exhaustive: * Money services, payment processing, or funds transmission (MSBs, PSPs, P2P platforms, prepaid/gift cards, ATMs) * Virtual asset service providers (VASPs), including crypto exchanges, wallet providers, custody or escrow services, and stablecoin issuers * Licensed gambling, betting, or gaming operators * Securities brokers or crowdfunding platforms * Businesses with complex or multi-layered ownership structures ## Prohibited Business Activities **Not supported under any circumstances.** BlindPay does not provide services to businesses engaged in the following activities, directly or indirectly, regardless of jurisdiction or licensing claims. | Category | Description | | --- | --- | | **Adult Content and Sexually Oriented Services** | Businesses offering or facilitating sexually explicit, obscene, or adult-oriented content or services. | | **Drugs, Controlled Substances, Alcohol and Pseudo-Pharmaceuticals** | Sale, distribution, or facilitation of illegal, controlled, or unregulated substances, including unlicensed pharmaceuticals. | | **Weapons, Ammunition and Explosives** | Entities trading or dealing in weapons, firearms, ammunition, or weapon-related products. | | **Gambling, Betting and Games of Chance (Unlicensed)** | Any business involving unlicensed or prohibited games of chance or gambling-related services. | | **Financial Crime-Linked Businesses and Fraudulent Models** | Ponzi schemes, pyramid schemes, or any model indicative of fraud, including unlicensed money services and shell banks. | | **Hate, Violence, Terrorism and Discriminatory Activity** | Activities promoting or enabling harm, hate, exploitation, or terrorism, including known terrorist organizations. | | **Sanctioned and Illicit Entities** | Entities or individuals on OFAC, EU, UN, or other sanctions lists, or operating from prohibited jurisdictions under comprehensive embargoes. | | **Politically Exposed Persons (PEPs)** | Businesses where a principal owner or controlling person is a PEP. Identified associations are declined during onboarding. | | **Identity Fraud, Anonymity Misuse and Shell Structures** | Anonymous or fictitious accounts, bearer share companies, or entities designed to obscure beneficial ownership. | | **High-Risk Lending and Predatory Financial Services** | Predatory or non-compliant financial products, including abusive lending practices. | | **Governmental, Diplomatic and Political Entities** | Government bodies, embassies, consulates, supranational organizations, and diplomatic entities. | | **Data Misuse and Consumer Privacy Violations** | Businesses that compromise customer data or privacy. | | **Intellectual Property Infringement and Counterfeit Goods** | Entities selling, distributing, or enabling IP-infringing or counterfeit products. | | **Tobacco and Tobacco-Related Products** | Manufacture, distribution, or sale of tobacco products and related services. | | **Charities and NGOs** | Non-governmental organizations, charitable foundations, and nonprofit entities. | | **Legal, Notarial and Professional Services** | Lawyers (including IOLTA accounts), notaries, trustees, and accountants. | | **Banking and Correspondent Entities** | Foreign banks, offshore banks, private banking, correspondent accounts, payable-through accounts, and concentration accounts. | | **Retail, Hospitality and Consumer Goods** | Liquor stores, convenience stores, restaurants, and jewelry or precious metals dealers. | | **Investment and Asset Management** | Non-deposit investment products, trust and asset management services, and trade finance activities. | | **Construction** | Construction companies, general contractors, and related service providers. | ## Consequences of Violation If prohibited activity is identified: * **Account Review**: The account will be reviewed and subject to suspension or termination * **Fund Freezing**: Funds may be frozen pending investigation * **Legal Notification**: Relevant partners and authorities may be notified in accordance with legal and contractual obligations ## Reporting Violations Report any prohibited activities on the platform to . ## Related * [Supported Countries](/docs/kb/supported-countries) · [Customers](/docs/kb/kyc) --- --- url: /docs/kb/information-requests.md description: >- A Request for Information (RFI) is how BlindPay compliance asks for missing KYC or KYB details before a customer can be approved. --- ## Summary A Request for Information (RFI) is how BlindPay's compliance team asks for missing or clarifying details when a customer's KYC or KYB review is incomplete. Instead of rejecting outright, BlindPay puts the customer on hold in `compliance_request` status, emails your team, and gives you 27 days to collect and submit the requested documents or explanations from your customer. This guide is the operational walkthrough; to handle RFIs programmatically, see the [RFI API documentation](/docs/kb/information-requests). ## When an RFI is created Compliance creates an RFI whenever a customer's submission is missing information, contradicts another document, or falls into a category that requires extra context. Common triggers include: * A business description that doesn't match the company's website or industry * A proof of address document that doesn't match the registered address * An identity document that's expired, blurry, or partially visible * An ownership structure that needs an updated share register or UBO documentation * Source of funds or source of wealth that needs supporting evidence Each RFI is built as a list of sections. Each section has a title, a description explaining what compliance needs, and one or more fields the customer must fill in (text, file upload, or dropdown). A single RFI can mix document uploads with free-text explanations. ## What happens to the customer When an RFI is created, the customer's `kyc_status` flips from its prior value to `compliance_request`. See [customer statuses](/docs/kb/kyc#statuses) for the full status list. While in this status: * The customer cannot send or receive funds * The customer sees a banner in their dashboard prompting them to submit the requested information * A 27-day countdown starts. If no response is submitted, the customer is automatically rejected The customer's previous status is snapshotted when the RFI is created. If your compliance contact cancels the RFI, the customer returns to that prior status instead of staying in `compliance_request`. ## Email notifications When an RFI is opened, BlindPay emails **every active member of your instance** so your team can coordinate with the customer. We do not email the customer directly, since collecting the documents and uploading them is your responsibility as the partner. The cadence is: | Day | Email | Purpose | | --- | --------------- | --------------------------------------------------------------------------- | | 0 | Action Required | Lists each section and the fields requested. Includes the deadline. | | 7 | Reminder | Sent only if the RFI is still pending. | | 17 | Final Notice | Sent only if the RFI is still pending. Warns of automatic rejection. | | 27 | Auto-Rejected | Sent if the deadline passes without a submission. The customer is rejected. | All emails come from `compliance@blindpay.com` and link to your dashboard at `app.blindpay.com`. Emails are sent to every non-deleted user in your instance. To control who receives them, manage your team's membership in **Settings → Members**. ## Responding to an RFI 1. Open the customer in your BlindPay dashboard. A banner highlights the open RFI. 2. Review each requested section with your customer and collect the documents or explanations. 3. Upload the files and submit the response in a single action. Once submitted: * The customer's `kyc_status` returns to `verifying` * BlindPay's compliance team re-reviews the application along with the new information * The pending email reminders for this RFI are stopped immediately **Submit the entire RFI at once.** The submission is single-shot. All required fields must be filled before you can send the response. ## RFIs for approved customers Compliance can also request information from a customer **without pausing them**. In that case the customer's `kyc_status` becomes `approved_rfi` instead of `compliance_request`, and: * The customer keeps sending and receiving funds normally while the RFI is open * Submitting the response restores them to `approved` immediately, with no re-review * Missing the 27-day deadline does **not** auto-reject them; our compliance team reviews the case manually instead This is typically used for periodic reviews or follow-up questions on customers that are already approved. The email cadence and the response flow in your dashboard are the same. ## What happens after submission After you submit, compliance reviews the response and either approves the customer or requests another round of information. Most RFIs are resolved within a few business days. If the response still doesn't meet our requirements, a new RFI may be opened with the additional questions. ## Deadlines and auto-rejection If 27 days pass without a submission, BlindPay automatically rejects the customer. The customer's `kyc_status` becomes `rejected` and a final email is sent to your team. This applies to customers in `compliance_request`; customers in `approved_rfi` are not auto-rejected (see above). To restart the KYC process for that customer, contact our compliance team. ## Best practices * **Keep your member list clean.** Only members who should see compliance correspondence should remain on the instance. Remove former teammates promptly. * **Coordinate with your customer early.** The 27-day window is firm, and most rejections happen because the customer wasn't reached in time. * **Match the format compliance asks for.** If a section requires a utility bill, a bank statement won't satisfy it. Re-read each section before uploading. * **Submit complete responses.** Partial submissions are not accepted; the dashboard validates every required field before letting you send. ## Related * [RFI API documentation](/docs/kb/information-requests) · [Customer statuses](/docs/kb/kyc#statuses) * [Basic Customer Information (KYC)](/docs/kb/kyc-basics) · [Basic Business Information (KYB)](/docs/kb/kyb-documents) --- --- url: /docs/kb/instance-requests.md description: >- How BlindPay compliance asks your team for additional information about your own account, and how to respond before the 27-day window closes. --- An Instance RFI is a Request for Information directed at your instance (your BlindPay account) rather than at one of your customers. Compliance uses it to collect updated business information or supporting documents from your team, for example during a periodic review. For RFIs about a specific customer, see [Information requests](/docs/kb/information-requests). ## How it differs from a customer RFI | Aspect | Customer RFI | Instance RFI | | --- | --- | --- | | Who it is about | One of your customers | Your own company | | Where you answer | The customer's profile page | Your instance settings page | | Effect while open | That customer cannot transact | No effect on your account or payments | | Missed deadline | The customer is auto-rejected | The request expires, nothing else happens | ## When an instance RFI is created Compliance creates an instance RFI whenever it needs information about your company itself. Common triggers include: * A periodic review of your account requires refreshed documents * Your business description or website no longer matches what we have on file * Ownership changes need an updated share register or UBO documentation * A proof of address or source of funds document needs to be renewed Each RFI is built as a list of sections. Each section has a title, a description explaining what compliance needs, and one or more fields to fill in (text, file upload, or dropdown). ## Email notifications When an instance RFI is opened, BlindPay emails **every active member of your instance**. The cadence is: | Day | Email | Purpose | | --- | --- | --- | | 0 | Action Required | Lists each section and the fields requested. Includes the deadline. | | 7 | Reminder | Sent only if the RFI is still pending. | | 17 | Final Notice | Sent only if the RFI is still pending. | | 27 | Expired | Sent if the deadline passes without a submission. | All emails come from `compliance@blindpay.com` and link to your dashboard at `app.blindpay.com`. Emails are sent to every non-deleted user in your instance. To control who receives them, manage your team's membership in **Settings → Members**. ## Responding to an instance RFI 1. Open **Settings → Instance** in your BlindPay dashboard. A "Request for Information" section highlights the open RFI. 2. Review each requested section and collect the documents or explanations. 3. Upload the files and submit the response in a single action. Once submitted, the RFI moves to `submitted`, the reminder emails stop, and compliance reviews the response. If anything else is needed, a new RFI is opened. **Submit the entire RFI at once.** The submission is single-shot. All required fields must be filled before you can send the response. ## Deadlines and expiry If 27 days pass without a submission, the RFI is marked expired and a final email is sent to your team. Unlike customer RFIs, **nothing happens to your account automatically**: no status changes, no blocked payments. Our compliance team follows up with you directly if the information is still required. ## Best practices * **Answer from the dashboard.** The instance settings page validates every required field before letting you send. * **Keep your member list clean.** Only members who should see compliance correspondence should remain on the instance. * **Match the format compliance asks for.** If a section requires a utility bill, a bank statement won't satisfy it. ## Related * [Information requests](/docs/kb/information-requests): RFIs about a specific customer * [KYB documents](/docs/kb/kyb-documents): business verification document requirements --- --- url: /docs/kb/rejection-reasons.md description: >- Reason codes and messages returned when an application or document is rejected during verification. --- ## Summary When an application or document is rejected during verification, BlindPay returns a reason code and message explaining the outcome. The table below lists every code, from document-quality issues (corners cut off, low resolution, photocopies) to compliance and commercial decisions. Most document rejections can be resolved by re-uploading a clear, full, in-date original. ## Reason codes | # | Reason Code | Reason Message | | --- | --- | --- | | 1 | Business activity | The business industry classification (NAICS, CNAE or similar) listed in the articles of incorporation does not match the company's stated business activity. | | 2 | Commercial | Commercial Reason. | | 3 | Compliance Verification | This decision is based on non-compliance with the criteria established in our Internal Compliance Policies and risk assessment guidelines. For reasons of confidentiality and security, we do not provide specific details regarding our internal approval criteria. | | 4 | Document corners cut off | We were unable to accept the Identity Document because some edges or corners were cut off in the photo. Please re-upload a clear, full-page photo, ensuring that all four corners of the document are completely visible within the frame. | | 5 | Document not readable | We were unable to verify the Identity Document because the document image provided is blurry, has glare, or is too low resolution. Please upload a new, clear photo, ensuring that all four corners of the document are visible and all text is easy to read. | | 6 | Document photo with low quality | We were unable to verify the Identity Document because the document image provided is blurry, has glare, or is too low resolution. Please upload a new, clear photo, ensuring that all four corners of the document are visible and all text is easy to read. | | 7 | Duplicated | Duplicated Account. | | 8 | Expired | The documentation provided is expired. Please submit a valid document. | | 9 | Incorrect Proof of Address Document (POA) | The document provided does not meet our [compliance requirements](/docs/kb/proof-of-address). Please ensure it is less than 90 days old, shows the full page, contains an emission date, and is issued under the user/entity name. | | 10 | Low quality selfie | We couldn't verify the selfie because it was blurry, dark, or out of focus. Please restart the onboarding process and try again with a clearer, well-lit selfie. | | 11 | Nested | [Nested Payments](/docs/kb/nested-payments). | | 12 | No Response to RFI | Application rejected due to customer failure to respond to Enhanced Due Diligence (EDD) / [RFI](/docs/kb/information-requests) requests. | | 13 | Photocopy | New identification document is required. The attached ID is a scan, photocopy, picture of a screen, or a cropped image. Please upload a fresh photo of the original document taken directly from the webcam or phone camera. | | 14 | Reversal Payments | Reversal Payments. | ## Related * [KYC Basics — photo tips](/docs/kb/kyc-basics#photo-tips) · [Proof of Address](/docs/kb/proof-of-address) * [Requests for Information](/docs/kb/information-requests) --- --- url: /docs/introduction.md description: >- BlindPay is a global payment API that moves money over bank rails and stablecoins from a single integration. --- BlindPay is a global payment infrastructure API. One integration covers both traditional bank transfers and stablecoin payments, so your product can move money across borders without stitching together separate rails. Under the hood, every payment settles through stablecoins. BlindPay is a non-custodial payment processor: your funds stay under your control throughout the process, and if a transaction fails, funds are automatically returned to the originating wallet. You don't need to think about that layer to use the fiat side of the API, but it's why BlindPay can move money between bank rails and blockchains in the first place. ## What BlindPay does The API is organized around four verbs that work the same way whether you think in fiat or in stablecoins. | Verb | Fiat | Stablecoin | | --- | --- | --- | | **Store** | N/A (value settles as stablecoin in the linked wallet) | Managed wallet (or external blockchain wallet) | | **Receive** | Bank transfer in (payin) | On-ramp: fiat in, stablecoin delivered to a wallet | | **Send** | Pay out to a bank account (payout) | Off-ramp: stablecoin pulled from a wallet, fiat out | | **Transfer** | N/A | Cross-chain stablecoin transfer | ## Two flavors, one API The docs are split into two flavors based on how you think about money movement. Both run on the same engine, same authentication, same instances, and same webhooks; pick the lens that matches your product. * **[Fiat](/docs/overview)** is for builders who think in bank rails: money in over ACH, wire, SWIFT, or Pix, and money out to a bank account. A virtual account is a customer's dedicated deposit account, payins are deposits, and payouts are bank transfers. Value settles as stablecoin in the linked wallet behind the scenes. * **[Stablecoin](/docs/overview)** is for crypto-native builders. It covers managed and external blockchain wallets, chains and tokens, on-chain authorization (ERC-20 approve, Stellar signing, Solana delegation), and cross-chain transfers. You can switch flavors at any time from the sidebar; nothing about your account or API key changes. ## How it works Every payment flows through a customer that has completed KYC. BlindPay handles the compliance layer: you collect the data, BlindPay verifies it. Once a customer is approved, every payment follows the same three-step pattern: ### Quote Create a quote for the amount you want to move. The quote locks in the exchange rate and fees for a short window (payout and payin quotes expire in 5 minutes; transfer quotes in 15 seconds). ### Authorize Depending on the flow, this means approving a bank account, signing a wallet authorization, or simply holding a valid quote, no extra step needed for most fiat payins. ### Execute Submit the payment against the quote before it expires. BlindPay moves the funds and reports status changes over webhooks. BlindPay is non-custodial: your funds remain under your control throughout the process. If a payment can't settle, funds are returned to the originating wallet or bank account. ## Get started ### Pick your flavor Read the [fiat](/docs/overview) or [stablecoin](/docs/overview) index to see which lens matches what you're building. Most teams only need one. ### Create an instance An [instance](/docs/learn/instances) is your sandboxed or production environment, with its own API keys, customers, and webhooks. ### Follow a quickstart [Bank transfer to stablecoins](/docs/quickstart-payin), or [stablecoins to bank transfer](/docs/quickstart-payout). ## Related * [Fiat](/docs/overview): bank rails, virtual accounts, payins and payouts * [Stablecoin](/docs/overview): wallets, chains, on-ramp and off-ramp * [Instances](/docs/learn/instances): sandbox vs production environments * [Build with AI](/docs/build-with-ai): use BlindPay from an AI coding assistant --- --- url: /docs/overview.md description: >- Connect to bank rails or stablecoin networks (or both) through one REST API. Issue virtual accounts and move fiat payins and payouts, or hold, send, and receive USDC and USDT across chains. --- BlindPay's fiat APIs connect your product to bank rails in the US, Brazil, Mexico, Argentina, Colombia, and Europe. Issue a virtual account in each customer's name, accept deposits over ACH, wire, or Pix, and pay out to any bank account (including SWIFT), all through a single REST API. Under the hood, every dollar or real you move settles through stablecoins. You don't need to think about that layer to use the fiat APIs: BlindPay manages the conversion step for you and hands you bank-rail primitives (virtual accounts, payins, payouts) on top. If you want the crypto mechanics, switch to the Advanced flavor of this page. BlindPay is non-custodial under the hood: funds always move through a customer that has completed KYC, and a quote (which locks the rate and fees) before any transfer executes. ## What's available | Capability | Description | | --- | --- | | Virtual accounts | A dedicated US bank account issued in each customer's name, with its own routing and account number | | Receive (payins) | Accept ACH, wire, RTP, SWIFT, Pix, SPEI, Transfers, or PSE deposits; every incoming payment is tracked as a payin | | Send (payouts) | Pay out to any bank account: ACH, wire, SWIFT, RTP, Pix, SPEI, ACH COP, Transfers, or SEPA | | Webhooks | Real-time events for every deposit and payout state change | ## Supported corridors | Payment method | Currency | Direction | Settlement | | --- | --- | --- | --- | | ACH | USD | Receive | Up to 5 business days | | ACH | USD | Send | ~2 business days | | Wire | USD | Receive | Up to 5 business days | | Wire | USD | Send | ~1 business day | | SWIFT (international) | USD | Receive | Up to 5 business days | | SWIFT (international) | USD | Send | ~5 business days | | RTP | USD | Receive | Instant | | RTP | USD | Send | Instant | | Pix | BRL | Receive + send | Instant (up to 5 min to confirm) | | SPEI | MXN | Receive + send | Instant (up to 10 min to confirm) | | Transfers | ARS | Receive + send | Instant (up to 10 min to confirm) | | PSE | COP | Receive | Up to 10 min (payment link) | | ACH COP | COP | Send | ~1 business day | | SEPA | EUR | Send | ~1 business day | Receive-side timing applies to production instances. On development instances, every payin auto-completes about 30 seconds after initiation, regardless of method. ## The fiat model The same model applies, read through a bank-rails lens: | Verb | What it means in fiat terms | Docs | | --- | --- | --- | | Receive | A bank deposit (ACH, wire, Pix, SPEI, Transfers, PSE) arrives and is recorded as a payin | [Bank transfer in](/docs/payins) | | Send | A payout moves funds out to any bank account you've added for a customer | [Pay out to bank](/docs/payouts) | ## How funds move Every payin and payout still settles through stablecoins behind the scenes. You don't sign anything on-chain to use the fiat APIs (BlindPay custodies the conversion step); these diagrams show what happens after you call the API. ### Fiat in (payin) 1. **Deposit.** The sender moves funds from their bank account to either a virtual account issued for that customer, or BlindPay's own bank details with a memo code. 2. **Confirmation and delivery.** Once the deposit is confirmed, BlindPay completes the payin and the equivalent value lands wherever you configured it to (a managed wallet balance, ultimately payable out again, or an external wallet if you're integrating with the stablecoin side). ### Fiat out (payout) 1. **Authorization.** Funds are authorized for the payout amount (handled for you when paying out of a BlindPay-managed wallet balance; see [Pay out to bank](/docs/payouts)). 2. **Collection and conversion.** BlindPay collects the authorized funds and starts the fiat transfer over the local rail: ACH or wire in the US, Pix in Brazil, SPEI in Mexico, Transfers in Argentina, and so on. 3. **Settlement.** Once the receiving bank confirms the transfer, the payout completes and the recipient has their funds in local currency. If the fiat transfer can't settle or gets returned, funds are sent back automatically. Nothing sits in limbo. ## Get started * [Bank transfer to stablecoins](/docs/quickstart-payin): create a customer and accept a deposit, step by step * [Virtual accounts](/docs/virtual-accounts): how virtual accounts work, approval stages, and required fields * [Bank transfer in](/docs/payins): payin quotes, payment methods, and what to show the payer * [Pay out to bank](/docs/payouts): add a bank account and send a payout ## Related * [Introduction](/docs/introduction) * [Learn: instances](/docs/learn/instances) * [Learn: webhooks](/docs/learn/webhooks) * [Knowledge base: KYC](/docs/kb/kyc) * Switch to the Advanced flavor of this page for the stablecoin mechanics BlindPay's stablecoin APIs connect your product to multiple blockchains and fiat payment rails from a single REST API. Hold USDC and USDT in a wallet, accept fiat and deliver stablecoins on-ramp, pull stablecoins and pay out fiat off-ramp, or move stablecoins cross-chain. BlindPay does not provide wallet services. We operate as a non-custodial payment processor: your funds stay under your control throughout the flow. If a transaction fails, funds are automatically returned to the originating wallet. ## What's available | Capability | Description | | --- | --- | | Managed wallets | BlindPay-custodied wallets that hold USDC/USDT per customer (beta, Arbitrum + Polygon) | | External blockchain wallets | Customer-controlled wallets you register to receive or authorize stablecoin movement | | Payins (on-ramp) | Accept a fiat deposit and deliver stablecoins to any wallet | | Payouts (off-ramp) | Pull stablecoins from a wallet and pay out fiat to a bank account | | Send / Receive | Move stablecoins in and out of managed wallets (transfers in beta; USDC can move cross-chain via Circle CCTP v2, other tokens stay same-network) | | Webhooks | Real-time events for every wallet deposit, payout, and transfer | ## Managed wallets vs external blockchain wallets BlindPay supports two ways to hold the stablecoin side of a customer's funds. Pick one per customer, or use both. | | Managed wallet (`bl_...`) | External blockchain wallet (`bw_...`) | | --- | --- | --- | | Custody | BlindPay-custodied | Customer-controlled (EOA or account abstraction) | | Created via | `POST /customers/{re}/wallets` | `POST /customers/{re}/blockchain-wallets` | | Networks | Arbitrum, Polygon (beta) | Any supported chain | | Signing required | None, BlindPay moves funds on your behalf | ERC-20 `approve`, Stellar signed XDR, or Solana token delegation depending on chain | | Typical use | Hold a balance inside BlindPay between on-ramp and off-ramp | Receive funds into a wallet you already control, or fund a payout from your own wallet | | Add securely | n/a | Sign a message (no manual address entry) | | Add non-secure | n/a | Paste the address directly (funds are lost if the address is wrong) | A customer must exist and complete KYC before you can create either kind of wallet. See [Payins](/docs/payins) and [Payouts](/docs/payouts) for how each wallet type plugs into a payin or payout. ## Supported chains and tokens | Chain | Mainnet | Testnet | Tokens | | --- | --- | --- | --- | | Ethereum | `ethereum` | `sepolia` | USDC, USDT | | Base | `base` | `base_sepolia` | USDC, USDT | | Polygon | `polygon` | `polygon_amoy` | USDC, USDT | | Arbitrum | `arbitrum` | `arbitrum_sepolia` | USDC, USDT | | Stellar | `stellar` | `stellar` (testnet) | USDC | | Solana | `solana` | `solana_devnet` | USDC, USDT | | Tron (beta) | `tron` | n/a | USDT only | On development instances, every chain above also accepts **USDB**: a BlindPay test ERC-20/asset used to simulate transactions without real funds. Mint it from the dashboard (EVM) or via the create-asset-trustline and mint endpoints (Stellar, Solana). See [Mint USDB](/docs/mint-usdb) for the full minting steps. Authorization mechanics differ per network when moving stablecoins out of an external wallet: | Network type | Authorization | | --- | --- | | EVM (Ethereum, Base, Polygon, Arbitrum) | Standard ERC-20 `approve` | | Stellar | Authorize endpoint, then a signed XDR transaction | | Solana | Token delegation: prepare, sign, submit | ## The verbs, in crypto terms | Verb | What happens | Docs | | --- | --- | --- | | Store | Hold USDC/USDT in a BlindPay-managed wallet | [Store](/docs/store) | | Send | Stablecoins move out of a managed wallet to another wallet or address | [Send](/docs/send) | | Receive | Stablecoins arrive in a managed wallet, tracked via `wallet.inbound` | [Receive](/docs/receive) | | Payin | On-ramp: a fiat deposit is converted and the stablecoin is delivered to a wallet | [Payins](/docs/payins) | | Payout | Off-ramp: stablecoins are pulled from a wallet and paid out as fiat to a bank account | [Payouts](/docs/payouts) | ## Non-custodial flow of funds BlindPay never takes custody of your stablecoins beyond the single transaction it is asked to execute. Off-ramp (send) and on-ramp (receive) each move through three steps. ### Off-ramp: stablecoin to fiat 1. **Token authorization**: your wallet authorizes BlindPay to move the exact stablecoin amount needed (ERC-20 `approve` on EVM, a signed XDR on Stellar, or token delegation on Solana). 2. **Token collection and fiat conversion**: BlindPay collects the authorized stablecoins and initiates the matching fiat transfer over the local banking rail (ACH/wire in the US, Pix in Brazil, and other region-specific rails). 3. **Settlement confirmation**: once the receiving bank confirms the fiat transfer, the payout is finalized and the recipient receives funds in their local currency. If the fiat transfer can't settle, or it gets returned, the stablecoins are sent back to the same wallet that authorized the payout. ### On-ramp: fiat to stablecoin 1. **Fiat deposit**: funds move from a sender's bank account to a BlindPay bank account or a dedicated virtual account. 2. **Stablecoin delivery**: once the deposit is confirmed, BlindPay mints or transfers the equivalent stablecoin amount to the destination wallet, a managed wallet (`wallet_id`) or an external blockchain wallet (`blockchain_wallet_id`), at the rate locked in the quote. ## Get started * [Stablecoins to bank transfer](/docs/quickstart-payout): the step-by-step payout quickstart * [Store](/docs/store): hold stablecoins per customer in a managed wallet * [Payins](/docs/payins): accept fiat deposits and deliver stablecoins * [Payouts](/docs/payouts): pull stablecoins and pay out fiat * [Send](/docs/send): move stablecoins between wallets ## Related * [Introduction](/docs/introduction) * [Supported chains](/docs/kb/supported-chains) * [Sandbox vs production](/docs/learn/sandbox-vs-production) * [Webhooks](/docs/learn/webhooks) --- --- url: /docs/build-with-ai.md description: >- Connect AI coding agents and AI builders to BlindPay with an MCP server, Agent Skills, and a REST API. --- BlindPay exposes its full payments API through three surfaces that drop into any AI coding agent or AI app builder: an **MCP server**, **Agent Skills**, and a **REST API**. Use them to create quotes, execute payments, manage customers, and configure webhooks directly from the terminal, your IDE, or a no-code builder. ## The three surfaces ### MCP server The Model Context Protocol server exposes the BlindPay API as callable tools. Any MCP-compatible agent (Codex, Claude Code, Cursor, Windsurf, and others) can call these tools to move money in response to natural-language prompts. ```bash [Codex] codex mcp add blindpay \ --env BLINDPAY_API_KEY=YOUR_API_KEY \ --env BLINDPAY_INSTANCE_ID=in_000000000000 \ -- npx -y @blindpay/mcp ``` ```bash [Claude Code] claude mcp add blindpay \ --env BLINDPAY_API_KEY=YOUR_API_KEY \ --env BLINDPAY_INSTANCE_ID=in_000000000000 \ -- npx -y @blindpay/mcp ``` For Claude Code or another JSON-configured MCP host, add it directly to `.mcp.json` in your project: ```json [.mcp.json] { "mcpServers": { "blindpay": { "command": "npx", "args": ["-y", "@blindpay/mcp"], "env": { "BLINDPAY_API_KEY": "YOUR_API_KEY", "BLINDPAY_INSTANCE_ID": "in_000000000000" } } } } ``` ### Agent Skills A packaged knowledge layer that teaches the agent BlindPay's rails, corridors, fees, and API patterns, so generated integrations are correct on the first pass. ```bash [Terminal] npx skills add blindpaylabs/blindpay-skills ``` ### REST API For builders that generate code but do not run an MCP host (Lovable, v0, Bolt, Replit), call the REST API directly from a backend route or server action. ```bash [cURL] curl https://api.blindpay.com/v1/instances/in_000000000000/payouts \ -H "Authorization: Bearer YOUR_API_KEY" ``` ## What you can ask Once connected, the agent can: * Create payout and payin quotes, and preview fees and FX rates * Execute payouts across EVM chains, Stellar, and Solana * Create and verify customers * Generate virtual accounts * Configure webhooks * Query transaction status ```text [Prompt] Use the BlindPay tools to create a payout quote for 1000 USDC to a USD ACH bank account. Show me the fees and rate, then execute after I confirm. ``` Money-movement tools should require explicit confirmation before executing. The MCP server does not gate confirmation on its own: build a confirmation step into your agent prompt or app flow before any payout, payin, or transfer is executed. ## Per-platform setup | Platform | Method | Notes | | --- | --- | --- | | Codex | `codex mcp add` | Run `codex mcp add` from the terminal, then load Agent Skills for full scaffolding. The CLI, IDE extension, and desktop app share the configuration. | | Claude Code | `claude mcp add` or `.mcp.json` | Run `claude mcp add` from the terminal, or add the server to `.mcp.json` and load Agent Skills for full scaffolding. | | Cursor | `.cursor/mcp.json` | Create the file, restart Cursor, then enable the `blindpay` server in Settings -> MCP. | | Windsurf | MCP settings | Add the same server config in Windsurf's MCP configuration. | | Lovable | Copy-paste prompt + REST API | No MCP host. Paste a prompt describing the backend function and add `BLINDPAY_API_KEY` / `BLINDPAY_INSTANCE_ID` as secrets. | | v0 | Copy-paste prompt + REST API | Paste a prompt describing a Next.js route handler; add credentials as Vercel environment variables. | | Bolt | Copy-paste prompt + REST API | Paste a prompt describing a server route; add credentials as environment variables in the Bolt project. | | Replit | Copy-paste prompt + REST API | Give the prompt to Replit Agent; add credentials in Replit Secrets (Tools -> Secrets). | ### Codex, Claude Code, Cursor, and Windsurf (MCP) These hosts call BlindPay tools directly, so the agent can run multi-step flows (quote, confirm, execute) in one conversation. Add the MCP server as shown above, then optionally layer in Agent Skills for richer domain knowledge of corridors and fees. ### Lovable, v0, Bolt, and Replit (prompt + REST API) These builders generate application code rather than hosting MCP tools, so the integration runs through a backend function calling the REST API. The pattern is the same across all four: ```text [Builder prompt] Add stablecoin payments to this app using the BlindPay API (https://api.blindpay.com). - Create a backend route/function that calls BlindPay to: 1. Create a payout quote (POST /v1/instances/{instance_id}/quotes) 2. Execute a payout (POST /v1/instances/{instance_id}/payouts/evm) - Read BLINDPAY_API_KEY and BLINDPAY_INSTANCE_ID from environment variables/secrets. - Authenticate with: Authorization: Bearer ${BLINDPAY_API_KEY} - Build a form where a user enters an amount in USDC and a destination (bank account / blockchain wallet), shows the live quote, and submits the payout. - Never expose the API key in client code: all BlindPay calls go through the backend. ``` ```ts [Backend route] const res = await fetch( `https://api.blindpay.com/v1/instances/${instanceId}/quotes`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.BLINDPAY_API_KEY}`, 'Content-Type': 'application/json', }, // request_amount is in minor units; 10000000 = 10 USDC at 6 decimals body: JSON.stringify({ currency_type: 'sender', request_amount: 10000000 }), }, ) const quote = await res.json() ``` Keep the secret API key server-side in all four builders: a backend/edge function, a Next.js route handler, or a server-only environment variable. Never call BlindPay from client-side code. ## Related * [API reference](https://api.blindpay.com/reference) * [Fiat overview](/docs/overview) * [Stablecoin overview](/docs/overview) * [API keys](/docs/learn/api-keys) * [Webhooks](/docs/learn/webhooks) --- --- url: /docs/sdks.md description: >- Official BlindPay SDKs for Node.js, Python, Go, PHP, and Swift, plus the OpenAPI spec and REST API reference. --- BlindPay ships official SDKs for Node.js, Python, Go, PHP, and Swift. Every SDK wraps the same REST API, so for any other language you can generate a typed client from the OpenAPI spec or call the REST API directly. ## Official SDKs | SDK | Install | Repository | | --- | --- | --- | | Node.js / TypeScript | `npm install @blindpay/node` | [blindpay-node](https://github.com/blindpaylabs/blindpay-node) | | Python | `pip install blindpay` | [blindpay-python](https://github.com/blindpaylabs/blindpay-python) | | Go | `go get github.com/blindpaylabs/blindpay-go` | [blindpay-go](https://github.com/blindpaylabs/blindpay-go) | | PHP | Composer | [blindpay-php](https://github.com/blindpaylabs/blindpay-php) | | Swift | Swift Package Manager | [blindpay-swift](https://github.com/blindpaylabs/blindpay-swift) | Each repository's README has the full install and usage guide. ## OpenAPI spec Download the OpenAPI 3.1 spec to generate a typed client in any language: ```bash curl https://api.blindpay.com/doc -o blindpay-openapi.json ``` Use [openapi-typescript](https://github.com/openapi-ts/openapi-typescript) for TypeScript: ```bash npx openapi-typescript https://api.blindpay.com/doc -o src/blindpay.d.ts ``` ## REST API The base URL for all API requests is: ``` https://api.blindpay.com/v1 ``` Authenticate with a Bearer token in every request: ```bash curl https://api.blindpay.com/v1/instances/in_000000000000/customers \ --header 'Authorization: Bearer YOUR_API_KEY' ``` The full API reference, including all endpoints, request bodies, and response schemas, is at: [api.blindpay.com/reference](https://api.blindpay.com/reference) --- --- url: /docs/quickstart-payin.md description: >- Accept a bank transfer and have BlindPay deliver the equivalent stablecoins automatically on a development instance, using only the REST API. --- This guide walks through the minimum steps to receive a fiat payment: accept the terms of service, create a customer, give that customer a stablecoin delivery target, quote the payin, and create it. On a development instance the deposit settles automatically about 30 seconds after you create the payin. This quickstart walks through an on-ramp payin: a sender pays fiat over bank rails and BlindPay delivers the equivalent stablecoins into a managed wallet. You will accept the terms of service, create a customer, create a managed wallet, quote the payin, and create it. Every step is a REST call, no on-chain signing involved. On a development instance the deposit settles automatically about 30 seconds after you create the payin. ## Before you begin You need a BlindPay account and an API key for a development instance. See [Instances](/docs/learn/instances) and [API keys](/docs/learn/api-keys). ### Accept terms of service Every instance requires a terms of service acceptance before you can create customers. For testing, you can accept on behalf of the customer; in production, your customer should accept the terms themselves. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/e/instances/in_000000000000/tos \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "idempotency_key": "" }' ``` Open the URL from the response in your browser, accept the terms, and copy the `tos_id` shown on the confirmation screen. You will pass this `tos_id` when you create the customer in the next step. ### Create a customer Every payment flows through a customer that has completed KYC. This example creates an individual customer with standard KYC. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "tos_id": "to_000000000000", "type": "individual", "kyc_type": "standard", "email": "email@example.com", "tax_id": "12345678", "address_line_1": "8 The Green", "address_line_2": "#12345", "city": "Dover", "state_province_region": "DE", "country": "US", "postal_code": "02050", "ip_address": "127.0.0.1", "phone_number": "+13022006100", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://example.com/proof-of-address.jpg", "first_name": "John", "last_name": "Doe", "date_of_birth": "1998-01-01T00:00:00Z", "id_doc_country": "US", "id_doc_type": "PASSPORT", "id_doc_front_file": "https://example.com/passport-front.jpg", "selfie_file": "https://example.com/selfie.jpg" }' ``` Save the `id` from the response: this is your customer ID (`re_...`). On development instances, KYC is approved automatically. In production, automated review for standard KYC takes about 60 seconds. ### Create a managed wallet A payin needs somewhere to deliver the stablecoins once the fiat arrives, so it asks for a wallet ID rather than a bank detail. The simplest destination is a [managed wallet](/docs/wallets): BlindPay generates the address and custodies the balance, so there is no external wallet to connect and nothing to sign. The payin needs somewhere to deliver the stablecoins once the fiat arrives. A [managed wallet](/docs/wallets) is the simplest destination: BlindPay generates the address and custodies the balance, so there is nothing to sign and no external wallet to connect. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "network": "base_sepolia", "name": "Quickstart Wallet" }' ``` Save the `id` from the response: this is your managed wallet ID (`bl_...`). This wallet is settlement plumbing, not the part you build a UI around. If your customer wants the stablecoins delivered to a wallet they control instead, register a [blockchain wallet](/docs/blockchain-wallets) and pass its `blockchain_wallet_id` on the quote. And if you want deposits to arrive without memo codes, create a [virtual account](/docs/virtual-accounts) for the customer. If your customer wants stablecoins delivered to a wallet they control instead, register a [blockchain wallet](/docs/blockchain-wallets) and use its `blockchain_wallet_id` on the quote in the next step. ### Create a payin quote A payin quote locks in how much fiat the sender sends and how much the customer receives before you create the payin. Pass `wallet_id` to target the managed wallet; BlindPay detects the delivery network from the wallet. This example quotes an ACH payin with the sender covering the fee, so `request_amount` is the amount the sender sends. A payin quote locks in how much fiat the sender sends and how much the wallet receives before you create the payin. Pass `wallet_id` to target the managed wallet; BlindPay detects the delivery network from the wallet, so you never pass a network on a payin quote. This example quotes an ACH payin with the sender covering the fee, so `request_amount` is the amount the sender sends. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "wallet_id": "bl_000000000000", "currency_type": "sender", "cover_fees": true, "request_amount": 10000, "payment_method": "ach", "token": "USDB" }' ``` `request_amount` is an integer in minor units, so `10000` here means $100.00. On development instances the token is always `USDB`, BlindPay's test stablecoin. In production use `USDC` or `USDT`. Save the `id` from the response: this is your payin quote ID (`pq_...`). You have 5 minutes to create the payin before the quote expires. This example uses the default manual bank transfer. If the customer connected a bank account through [Plaid](/docs/bank-accounts#connect-with-plaid), pass its id as `funding_bank_account_id` instead and BlindPay pulls the funds automatically; see [Payins](/docs/payins#pull-funding-from-a-plaid-connected-account). ### Create the payin Create the payin from the quote ID. This is what generates the bank details your end user pays into. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payins/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "payin_quote_id": "pq_000000000000" }' ``` The response includes `memo_code` and `blindpay_bank_details`. Share these with the end user so they know where to send the ACH transfer and how to reference it. ```json [Response] { // ... "memo_code": "12345678", "blindpay_bank_details": { "routing_number": "121145349", "account_number": "621327727210181", "account_type": "Business checking", "beneficiary": { "name": "BlindPay, Inc.", "address_line_1": "8 The Green, #19364", "address_line_2": "Dover, DE 19901" }, "receiving_bank": { "name": "Example Bank, N.A.", "address_line_1": "1 Example Plaza", "address_line_2": "San Francisco, CA 94129" } } // ... } ``` If the customer has an enabled virtual account, the `memo_code` is ignored and the deposit goes straight to their dedicated account details instead. See [Virtual accounts](/docs/virtual-accounts). ## What happens next On a development instance, the payin settles automatically about 30 seconds after you create it. Two webhooks confirm it: `payin.complete` for the payin itself and `wallet.inbound` when the stablecoins land in the managed wallet. You can also check the wallet balance directly: ```bash [cURL] curl --request GET \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets/bl_000000000000/balance \ --header 'Authorization: Bearer YOUR_API_KEY' ``` In production, BlindPay waits up to 5 business days for an ACH or wire deposit to arrive before cancelling the payin if nothing shows up. Done. To receive another payment, create a new payin quote and repeat the last step. ## Related * [Bank transfer in](/docs/payins): full reference for every payment method, including Pix, SPEI, and PSE * [Virtual accounts](/docs/virtual-accounts): a dedicated deposit account whose deposits settle to the linked wallet * [Pay out to bank](/docs/payouts): send funds back out to a bank account * [Webhooks](/docs/learn/webhooks): handle payin and customer events in real time - [Payins](/docs/payins): full reference for every payment method, including Pix, SPEI, and PSE - [Managed wallet](/docs/wallets): the wallet entity this quickstart delivers to - [Stablecoins to bank transfer](/docs/quickstart-payout): the payout counterpart of this guide - [Webhooks](/docs/learn/webhooks): handle `payin.complete` and `wallet.inbound` in real time --- --- url: /docs/quickstart-payout.md description: >- Send your first off-ramp payout from a BlindPay-managed wallet to a bank account on a development instance, using only the REST API. --- This quickstart walks through a payout: funds leave a BlindPay-managed wallet and USD lands in a recipient's bank account. You will accept the terms of service, create a customer, create and fund a managed wallet, add a bank account, quote the payout, and execute it. Because BlindPay custodies the funding wallet, there is no on-chain signing or token approval; every step is a REST call. On a development instance the payout completes automatically a few seconds after you execute it. ## Before you begin You need a BlindPay account and an API key for a development instance. See [Instances](/docs/learn/instances) and [API keys](/docs/learn/api-keys). ### Accept terms of service Every instance requires a terms of service acceptance before you can create customers. For testing, you can accept on behalf of the customer; in production, your customer should accept the terms themselves. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/e/instances/in_000000000000/tos \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "idempotency_key": "" }' ``` Open the URL from the response in your browser, accept the terms, and copy the `tos_id` shown on the confirmation screen. You will pass this `tos_id` when you create the customer in the next step. ### Create a customer Every payout requires a customer that has completed KYC. This example creates an individual customer with standard KYC. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "tos_id": "to_000000000000", "type": "individual", "kyc_type": "standard", "email": "email@example.com", "tax_id": "12345678", "address_line_1": "8 The Green", "address_line_2": "#12345", "city": "Dover", "state_province_region": "DE", "country": "US", "postal_code": "02050", "ip_address": "127.0.0.1", "phone_number": "+13022006100", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://example.com/proof-of-address.jpg", "first_name": "John", "last_name": "Doe", "date_of_birth": "1998-01-01T00:00:00Z", "id_doc_country": "US", "id_doc_type": "PASSPORT", "id_doc_front_file": "https://example.com/passport-front.jpg", "selfie_file": "https://example.com/selfie.jpg" }' ``` Save the `id` from the response: this is your customer ID (`re_...`). On development instances, KYC is approved automatically. In production, automated review for standard KYC takes about 60 seconds. ### Create a managed wallet Every payout needs a funding source, the wallet the settlement stablecoins are pulled from. A [managed wallet](/docs/wallets) is the simplest one: BlindPay generates the address and holds the keys, so executing the payout later needs no approval or signature. This example creates it on Solana Devnet, the development network with a REST endpoint for minting test funds. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "network": "solana_devnet", "name": "Quickstart Wallet" }' ``` Save the `id` (`bl_...`) and the `address` from the response. You need the address for the next step and for executing the payout. ### Fund the wallet with USDB Mint USDB, BlindPay's development-only test stablecoin, straight into the managed wallet. Pass the wallet's `address` and the amount of USDB to mint: ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/mint-usdb-solana \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "address": "YOUR_WALLET_ADDRESS", "amount": "100" }' ``` The response returns `success: true` with the on-chain signature. The wallet now holds 100 USDB to pay out from. ### Add a bank account This is the payout destination, the bank account that receives the USD. This example adds a US ACH account. Use real, valid bank details, even on development. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "ach", "name": "Display Name", "beneficiary_name": "", "routing_number": "", "account_number": "", "account_type": "checking", "account_class": "individual" }' ``` Save the bank account ID (`ba_...`). ### Create a payout quote A quote locks the conversion rate and fees for 5 minutes. The `network` and `token` describe the funding wallet: `solana_devnet` and `USDB` for the wallet you just funded. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "bank_account_id": "ba_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 5000, "network": "solana_devnet", "token": "USDB" }' ``` `request_amount` is an integer in minor units, so `5000` here means $50.00. `cover_fees: false` means the fee is deducted from what the bank account receives, the common case. Save the quote ID (`qu_...`). ### Execute the payout Execute the payout by passing the quote ID and the managed wallet's address as the funding source. Because BlindPay custodies the wallet, there is no approve or delegation step; this single call moves the funds. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "YOUR_WALLET_ADDRESS" }' ``` The response returns the payout with `status: "processing"`. The endpoint is `/payouts/evm` regardless of the funding network; it handles managed wallets on every supported chain. ## What happens next On a development instance, the payout completes automatically a few seconds after you execute it. Check for the `payout.complete` webhook to confirm. In production, settlement timing depends on the payout rail; see [cut-off times](/docs/kb/cut-off-times). Done. To send another payment, create a new payout quote and execute it again; each quote is single-use. ## Related * [Pay out to bank](/docs/payouts): full reference for every payout rail, including Pix, SPEI, and SWIFT * [Bank transfer to stablecoins](/docs/quickstart-payin): the payin counterpart of this guide * [Managed wallet](/docs/wallets): the funding wallet entity used in this quickstart * [Webhooks](/docs/learn/webhooks): handle `payout.complete` and other events in real time This quickstart walks through an off-ramp payout: pulling USDB from a managed wallet and delivering USD to a bank account. You will accept the terms of service, create a customer, create and fund a managed wallet, add a bank account, create a payout quote, and execute the payout. Every step is a REST call, no on-chain signing involved. If you want to fund the payout from a self-custodied wallet instead (with the on-chain authorization that entails), follow the [Payout with EVM](/docs/payout-evm), [Stellar](/docs/payout-stellar), or [Solana](/docs/payout-solana) tutorials after this one. ## Before you begin You need a BlindPay account and an API key for a development instance. See [Instances](/docs/learn/instances) and [API keys](/docs/learn/api-keys). ## Steps ### Accept terms of service For testing you can accept the terms of service yourself. In production, your customer should be the one accepting them. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/e/instances/in_000000000000/tos \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "idempotency_key": "" }' ``` Open the returned URL in your browser, accept the terms, and copy the `tos_id` shown on the confirmation screen. You will need it to create the customer. ### Create a customer Every payout requires a customer that has completed KYC. On development instances, customers are auto-approved. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "tos_id": "to_000000000000", "type": "individual", "kyc_type": "standard", "email": "email@example.com", "tax_id": "12345678", "address_line_1": "8 The Green", "address_line_2": "#12345", "city": "Dover", "state_province_region": "DE", "country": "US", "postal_code": "02050", "ip_address": "127.0.0.1", "phone_number": "+1234567890", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/v4-460px-Get-Proof-of-Address-Step-3-Version-2.jpg.jpeg", "first_name": "John", "last_name": "Doe", "date_of_birth": "1998-01-01T00:00:00Z", "id_doc_country": "US", "id_doc_type": "PASSPORT", "id_doc_front_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/1000_F_365165797_VwQbNaD4yjWwQ6y1ENKh1xS0TXauOQvj.jpg", "selfie_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/selfie.png" }' ``` This is the full standard KYC payload (`kyc_type: "standard"`): identity document, selfie, and proof of address. Save the customer ID (`re_...`) from the response. ### Create a managed wallet The payout needs a funding source, the wallet the stablecoins are pulled from. A [managed wallet](/docs/wallets) is the simplest one: BlindPay generates the address and holds the keys, so executing the payout later needs no approval or signature. This example creates it on Solana Devnet, the development network with a REST endpoint for minting test funds. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "network": "solana_devnet", "name": "Quickstart Wallet" }' ``` Save the `id` (`bl_...`) and the `address` from the response. You need the address for the next step and for executing the payout. ### Fund the wallet with USDB Mint USDB, BlindPay's development-only test stablecoin, straight into the managed wallet. Pass the wallet's `address` and the amount of USDB to mint: ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/mint-usdb-solana \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "address": "YOUR_WALLET_ADDRESS", "amount": "100" }' ``` The response returns `success: true` with the on-chain signature. The wallet now holds 100 USDB to pay out from. See [Mint USDB](/docs/mint-usdb) for minting on other networks. ### Add a bank account This is the fiat destination for the payout, the bank account that receives the USD once the stablecoin is converted. This example adds a US ACH account. Use real, valid bank details, even on development. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "ach", "name": "Display Name", "beneficiary_name": "", "routing_number": "", "account_number": "", "account_type": "checking", "account_class": "individual" }' ``` Save the bank account ID (`ba_...`). ### Create a payout quote A quote locks the conversion rate and fees for 5 minutes. The `network` and `token` describe the funding wallet: `solana_devnet` and `USDB` for the wallet you just funded. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "bank_account_id": "ba_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 5000, "network": "solana_devnet", "token": "USDB" }' ``` `request_amount` is an integer in minor units, `5000` is $50.00. `cover_fees: false` means the customer's stablecoin amount absorbs the fee, the common case. Save the quote ID (`qu_...`). ### Execute the payout Execute the payout by passing the quote ID and the managed wallet's address as the funding source. Because BlindPay custodies the wallet, there is no `approve` call, signature, or delegation; this single call moves the funds. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "YOUR_WALLET_ADDRESS" }' ``` The response returns the payout with `status: "processing"`. On a development instance, the payout completes automatically a few seconds after creation, no manual settlement step needed. You will receive a `payout.complete` webhook once it does. Congratulations! You've completed your first off-ramp payout with BlindPay. To send another, create a new quote and execute again, each quote is single-use. ## Next steps This quickstart used a managed wallet, so no on-chain authorization was needed. To pull funds from a self-custodied wallet instead, follow the per-network tutorials: [EVM](/docs/payout-evm) (ERC-20 approve), [Stellar](/docs/payout-stellar) (authorize + signed XDR), or [Solana](/docs/payout-solana) (token delegation). ## Related * [Payouts](/docs/payouts): the full payout section, with a tutorial per funding path * [Payins](/docs/payins): accept fiat and deliver stablecoins to a wallet * [Managed wallet](/docs/wallets): the funding wallet entity used in this quickstart * [Webhooks](/docs/learn/webhooks): handle `payout.complete` and other real-time events * [KYC](/docs/kb/kyc): customer verification levels and required fields --- --- url: /docs/learn/instances.md description: >- Instances are isolated BlindPay environments, one per stage of your stack, created in the dashboard. --- An instance is an isolated BlindPay environment. If your product has separate development, staging, and production environments, create one instance for each. All features live inside an instance: customers, bank accounts, wallets, virtual accounts, payouts, and payins. Instances are created through the [BlindPay dashboard](https://app.blindpay.com). You cannot create them via the API. Each instance is either a `development` or `production` instance. Both expose the same features but differ in KYC handling, network availability, and whether fiat actually moves. All payouts and payins made on development instances skip the real fiat payment rails. Payouts do not move real money, and payins auto-complete a simulated deposit instead of waiting for a real bank transfer. ## Development vs. production | Feature | Development | Production | | --- | --- | --- | | Customers | Supported | Supported | | Bank accounts | Supported | Supported | | Payout quotes | Supported | Supported | | Payouts | Supported (no real fiat movement) | Supported | | Payin quotes | Supported | Supported | | Payins | Supported (auto-completes in ~30s) | Supported | | KYC | Auto-approved | Automated (~60s) or manual review | | EVM networks | `sepolia`, `base_sepolia`, `arbitrum_sepolia`, `polygon_amoy` | `ethereum`, `base`, `polygon`, `arbitrum` | | Stellar | Testnet | Mainnet | | Solana | Devnet | Mainnet | | Tron | N/A | Mainnet (beta) | | API keys | Supported | Supported | | Webhooks | Supported | Supported | On development instances, use USDB as the test stablecoin: a BlindPay-issued test token you can mint freely instead of sourcing real USDC or USDT. See [Sandbox vs. production](/docs/learn/sandbox-vs-production) for the full testing workflow. ## Create an instance ### Open the dashboard Go to the [BlindPay dashboard](https://app.blindpay.com) and click **Create instance**. ### Choose an environment Choose whether the new instance is `development` or `production`. Development instances are ready immediately. ### Wait for activation (production only) New production instances may take up to 3 business days to set up. See [Cut-off times](/docs/kb/cut-off-times) for full SLAs. ## API keys are per-instance An API key authenticates requests to one instance only. A key created for instance A will not work against instance B, even within the same organization. See [API keys](/docs/learn/api-keys) to create and manage keys. ## Related * [Sandbox vs. production](/docs/learn/sandbox-vs-production): behavior differences in depth * [API keys](/docs/learn/api-keys): create and manage instance keys * [Billing](/docs/learn/billing): how usage is charged per instance --- --- url: /docs/learn/sandbox-vs-production.md description: >- The exact behavioral differences between development and production instances, plus a checklist for switching over. --- Development and production instances expose the same API surface: same endpoints, same fields, same response shapes. What differs is how money, KYC, and reviews behave behind the scenes. This page lists every difference so nothing surprises you at go-live. ## KYC On development instances, every customer is auto-approved regardless of the documents you submit. Use any placeholder URLs for document fields, they are not actually verified. To test a rejection on development, set the first name (individuals) or legal name (businesses) to `Fail`. On production: | Verification type | Review timeline | | --- | --- | | KYC Standard | About 60 seconds, automatic | | KYC Enhanced | 3 hours to 1 business day, manual | | KYB Standard | 3 hours to 1 business day, manual | Customers from high-risk countries always require Enhanced KYC. Businesses (KYB) always require manual review. A customer's status starts as `verifying` and moves to `approved`, `rejected`, or `compliance_request` (an open request for information) as review completes. See [KYC](/docs/kb/kyc) for required fields, limits, and the RFI flow. On a rejection, BlindPay returns feedback in `kyc_warnings` or `fraud_warnings`. You cannot update an existing customer's KYC data; create a new customer with corrected information instead. ## Test token Development instances use **USDB**, a BlindPay-issued test stablecoin, in place of real USDC/USDT. You can mint unlimited USDB on EVM testnets, Stellar Testnet, and Solana Devnet; Solana Devnet has a REST endpoint that mints to any address, which makes it the easiest way to fund a managed wallet. See [Mint USDB](/docs/mint-usdb) for every method and the full request bodies. Real USDC or USDT cannot be used on a development instance. Production instances use real USDC/USDT and skip USDB entirely. ## Payout behavior On development instances, payouts skip the fiat payment rails entirely: there is no real bank transfer. Payouts still go through the same on-chain authorization step, but once you create the payout, the payout status moves to `completed` automatically instead of waiting on a real bank transfer. The authorization step itself is unchanged between development and production: ERC-20 `approve` on EVM chains, a signed XDR on Stellar, or token delegation on Solana. On production, the payout actually moves fiat to the recipient's bank account, with timing dependent on the bank account `type` (see the table in [Cut-off times](/docs/kb/cut-off-times)). ## Payin behavior On development instances, every payin auto-completes about 30 seconds after you call the create-payin endpoint, regardless of payment method. No real transfer needs to arrive. On production, BlindPay waits for the real payment to arrive before completing the payin. Arrival windows depend on `payment_method`: | Payment method | Currency | Arrival window | | --- | --- | --- | | `ach` | USD | Up to 5 business days | | `wire` | USD | Up to 5 business days | | `pix` | BRL | Up to 5 minutes | | `spei` | MXN | Up to 10 minutes | | `transfers` | ARS | Up to 10 minutes | | `pse` | COP | Up to 10 minutes | ## Virtual accounts On development instances, virtual accounts are auto-approved with no compliance or bank review. On production, virtual accounts go through a two-stage review: 1. **Compliance review**, status `pending_review` 2. **Bank review**, status `verifying` The result is `approved` or `rejected`. Neither stage guarantees approval. Issuance SLAs vary by account type; see [Virtual accounts](/docs/virtual-accounts) for the full table. You receive a webhook when the status changes either way. ## Webhooks Webhooks behave identically on development and production: same event names, same payload shapes, same signature verification. Use the webhook events dashboard on your development instance to inspect deliveries and replay events while you build, before relying on the same flow in production. See [Webhooks](/docs/learn/webhooks) for the full event list and signature verification. ## Testing amounts On development instances, you can force a payin or payout into a specific outcome by using one of these amounts as the `request_amount`: | Amount | Outcome | | --- | --- | | `666.00` | Failed | | `777.00` | Refunded | Any other amount completes successfully. These overrides only work on development; production processes the real amount you send. ## Switching to production ### Create a production instance Production instances are created in the [BlindPay dashboard](https://app.blindpay.com), the same way as development ones. New production instances can take up to 3 business days to provision. See [Instances](/docs/learn/instances). ### Generate a new API key API keys are scoped to a single instance. A development key will not authenticate against your production instance. See [API keys](/docs/learn/api-keys). ### Submit real KYC documents Production KYC is actually verified. Replace placeholder document URLs with real, valid documents (ID, selfie, proof of address) for every customer you create. ### Use real bank account details Bank account fields must be valid and reachable on production; there is no auto-approve to mask a typo'd routing number or account number. ### Remove testing amounts from your code Strip any logic that relies on `666.00` or `777.00` to simulate outcomes. On production these are processed as the real amounts they are. ### Switch from USDB to USDC/USDT Update `token` in your quote and mint requests from `USDB` to `USDC` or `USDT`, and point `network` at a production chain (`base`, `polygon`, `arbitrum`, `ethereum`, `stellar`, `solana`) instead of its testnet equivalent. ### Re-point webhooks Create a webhook endpoint on the production instance pointing at your production URL; the development instance's webhook configuration does not carry over. ## Related * [Instances](/docs/learn/instances): what an instance is and the dev/prod capability table * [API keys](/docs/learn/api-keys): per-instance authentication * [KYC](/docs/kb/kyc): verification levels, required fields, and limits * [Webhooks](/docs/learn/webhooks): events, payloads, and signature verification * [Cut-off times](/docs/kb/cut-off-times): production settlement windows by payment method --- --- url: /docs/learn/billing.md description: >- How BlindPay charges for virtual accounts, transactions, and partner fees, and when invoices go out --- BlindPay charges per active resource and per transaction. There is no setup fee and no monthly minimum. Development instances are always free. ## Virtual accounts Each active virtual account costs **$1.50 per month**. This applies to US virtual accounts; the fee is charged on your invoice at the end of the billing cycle, not when the account is created. | Account type | Cost | | --- | --- | | US virtual account | $1.50 / mo per active account | | Brazil virtual account | Not yet published | All incoming deposits to a virtual account automatically generate a payin. You are billed for the virtual account itself (if applicable), and the transaction fee for each payin is shown in the payin quote before it executes. See [virtual accounts](/docs/virtual-accounts) for how to create and manage virtual accounts. ## Transactions Transaction fees vary by payment method and corridor, so BlindPay does not publish a flat rate card. Instead, every quote (payin, payout, or transfer) discloses the exact fee before you execute, so you always see the full breakdown before committing any funds. The quote response includes: | Field | Meaning | | --- | --- | | `sender_amount` | The amount the sender needs to send | | `receiver_amount` | The amount the recipient receives | | `partner_fee_amount` | The partner fee portion, if you have one configured | | `flat_fee` | The flat fee portion, if any | | `billing_fee_amount` | The BlindPay billing fee, charged via invoice at the end of the month | Because the fee is locked into the quote at creation time, nothing changes between the quote and execution. If the quote expires before you execute, request a new one to get current pricing. For payins and payouts, `cover_fees` determines who absorbs the fee: `false` means your customer's recipient amount is reduced by the fee (the common case), `true` means the fee is added on top of what the sender pays. See [receive](/docs/payins) and [send](/docs/payouts) for the full breakdown. For payins, payouts, and transfers, `cover_fees` determines who absorbs the fee: `false` means the recipient amount is reduced by the fee (the common case), `true` means the fee is added on top of what the sender sends. See [payins](/docs/payins), [payouts](/docs/payouts), and [send](/docs/send) for the full breakdown. ## Partner fees You can add your own markup on top of BlindPay's fee using partner fees: a percentage or flat amount you configure per payin and per payout. BlindPay collects it from your customers as transactions happen, and releases the accumulated balance to you on the 1st of the following month, net of your own BlindPay invoice for that cycle. See [partner fees](/docs/learn/partner-fees) for how to configure, track, and withdraw partner fee revenue. ## Development instances Development instances never generate invoices. Virtual accounts, payins, payouts, and transfers on a development instance are free to use while you build and test. ## Invoices Invoices are generated at the end of each billing cycle and sent to the email address on your BlindPay account. Contact to update your billing contact. ## Related * [Partner fees](/docs/learn/partner-fees): add a markup on transactions and withdraw monthly revenue * [Instances](/docs/learn/instances): dev vs production capabilities * [Virtual accounts](/docs/virtual-accounts): account types, fees, and approval stages --- --- url: /docs/learn/partner-fees.md description: >- Add percentage or flat fees to your customers' transactions and withdraw the accumulated revenue monthly. --- ## What it is A partner fee is a markup you add on top of transactions processed through BlindPay. BlindPay collects the fee from your customer during each transaction, accumulates it over the calendar month, and releases the balance to you for withdrawal on the first day of the following month. ## Fee types You can configure two types of fees, set independently for payins and payouts: | Type | Description | | --- | --- | | Percentage | A percentage of the transaction amount | | Flat | A fixed amount per transaction | ## Monthly collection cycle Partner fees follow a monthly collection and withdrawal cycle: * Fees are collected automatically from your customer during each transaction, throughout the month. * All collected fees accumulate over the calendar month. * On the first day of the following month, the total balance is released and becomes available for withdrawal. * BlindPay nets your outstanding invoice out of the accumulated fees first. You never pay your BlindPay invoice separately; you receive the net amount. You receive a `payin.partnerFee` or `payout.partnerFee` webhook event as each fee is collected. Track collection status through the `tracking_partner_fee` object in quote and transaction responses. ## Configure a partner fee ### Create a fee configuration Create a fee configuration for payins, payouts, or both. Percentage fees are in basis points (`100` means 1%, capped at `1000` for 10%); flat fees are integers in minor units (`200` means $2.00). ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/partner-fees \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "name": "Display Name", "payin_percentage_fee": 100, "payin_flat_fee": 0, "payout_percentage_fee": 0, "payout_flat_fee": 200 }' ``` Save the `id` from the response: this is your partner fee ID (`pf_...`). You can also manage fee configurations from the [BlindPay dashboard](https://app.blindpay.com), under the instance's Partner Fees tab. To make a fee the default for all [virtual account](/docs/virtual-accounts) deposits, set `virtual_account_set: true` on creation (only one active fee per instance can hold the flag). See [Virtual accounts](#virtual-accounts) below for how defaults and per-account fees interact. ### Pass it in your quote requests Reference the `partner_fee_id` in a payin quote, payout quote, or transfer quote to apply that fee to the transaction. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 10000, "payment_method": "ach", "token": "USDC", "partner_fee_id": "pf_000000000000" }' ``` ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes', { method: 'POST', headers: { Authorization: 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ blockchain_wallet_id: 'bw_000000000000', currency_type: 'sender', cover_fees: false, request_amount: 10000, payment_method: 'ach', token: 'USDC', partner_fee_id: 'pf_000000000000', }), } ) const data = await response.json() ``` ## Virtual accounts Deposits into a [virtual account](/docs/virtual-accounts) create payins automatically, with no quote request where you could pass a `partner_fee_id`. Instead, the fee is configured ahead of time, at two levels: 1. **Instance-wide default.** Create a fee configuration with `virtual_account_set: true` (or toggle it in the dashboard's Partner Fees tab). It applies to every virtual account deposit on the instance. Only one active configuration can be the default. 2. **Per virtual account.** Pass a `partner_fee_id` when [creating a virtual account](/docs/virtual-accounts-create), or set it later with the update endpoint. A fee pinned to an account **overrides the instance-wide default** for that account's deposits. Update with `partner_fee_id: null` to clear the pin and fall back to the default. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/virtual-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "banking_partner": "cfsb", "token": "USDC", "blockchain_wallet_id": "bw_000000000000", "partner_fee_id": "pf_000000000000" }' ``` Deleting a fee configuration automatically unpins it from any virtual accounts referencing it; those accounts fall back to the instance-wide default. Every virtual account deposit always delivers at least $0.01 on-chain. The partner fee is collected from what remains after any transaction-time BlindPay fee, so on small deposits collection can be partial (a $5.00 fee on a $5.00 deposit collects $4.99) or zero on micro-deposits. See [fees on deposits](/docs/virtual-accounts#fees-on-deposits). ## Quote response fields Each payin quote, payout quote, and transfer quote response includes: | Field | Description | | --- | --- | | `partner_fee_amount` | Exact amount collected as a partner fee for this transaction | | `tracking_partner_fee.status` | Collection status | | `tracking_partner_fee.transaction_hash` | On-chain hash when the fee is delivered | | `tracking_partner_fee.completed_at` | Timestamp when fee delivery completed | ## Example A 1% payin fee and a $2.00 flat payout fee configured on the same instance: | Transaction | Customer pays | Partner fee collected | | --- | --- | --- | | $100 payin | $101.00 | $1.00 | | $100 payout | $102.00 | $2.00 | Monthly settlement example: | | Amount | | --- | --- | | Total partner fees collected in January | $500.00 | | BlindPay invoice for January | $250.00 | | Available for withdrawal on February 1 | $250.00 | ## Webhooks Subscribe to `payin.partnerFee` and `payout.partnerFee` to track fee collection in real time, instead of polling quote or transaction objects. ## Related * [Webhooks](/docs/learn/webhooks): full event reference and signature verification * [Billing](/docs/learn/billing): how BlindPay invoices your account * [Fiat receive](/docs/payins): payin quotes that accept `partner_fee_id` * [Fiat send](/docs/payouts): payout quotes that accept `partner_fee_id` * [Stablecoin send](/docs/send): transfer quotes that accept `partner_fee_id` * [Virtual accounts](/docs/virtual-accounts): automated deposits with default or per-account partner fees --- --- url: /docs/learn/customers.md description: >- People or businesses that send or receive payments and stablecoins through BlindPay. --- ## What it is A customer is an individual or business entity designated to interact with BlindPay. You can attach multiple bank accounts, blockchain wallets, and [BlindPay-managed wallets](/docs/wallets) to a customer. ## How it works For compliance and regulatory requirements, **every customer on your platform must be registered as a customer in BlindPay**. This is mandatory for transaction tracking and reporting. If any of your customers operate as money transmitters (entities that transfer funds on behalf of others), they must also register their end customers as customers in the system. This multi-level registration ensures complete transparency throughout the payment chain. Every customer must complete a KYC process to verify their identity before sending or receiving funds. ### Required fields We collect the following data per customer `type` (business, individual) and `kyc_type` (standard, enhanced). Fields marked with `*` are **optional**. **KYC/B Standard** | Individual | Business | | -------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | First name | Legal name | | Last name | Tax ID (government id number) | | Date of birth | Formation date | | Email | Email | | Country | Country | | Tax ID (government id number) | Doing business as\* | | Phone number | Website\* | | IP Address | IP Address | | Country | Country | | Address 1 | Address 1 | | Address 2\* | Address 2\* | | City | City | | State/province/region | State/province/region | | Postal code | Postal code | | ID Document - Country | UBOS + Shareholders above 25% (everything from Standard KYC but no Phone and IP Address) | | ID Document - Type (passport, id card, drivers license) | Company Formation Document | | ID Document - Front | Proof of Ownership Document | | ID Document - Back\* | Proof of Address - Type\* | | Proof of Address - Type (utility bill, bank statement)\* | Proof of Address - Document\* | | Proof of Address - Document\* | | | Selfie File | | **KYC Enhanced** — everything from KYC/B Standard, plus: | Individual | | ----------------------------------- | | Source of Funds Document Type | | Source of Funds Document File | | Purpose of Transactions | | Purpose of Transactions Explanation | All customers from [high risk countries](/docs/kb/supported-countries#high-risk-countries) must go through Enhanced KYC. Enhanced KYC individuals are manually verified by BlindPay's compliance team — this can take up to 1 business day and may require additional documents. For document quality and submission guidelines, see [KYC Basics](/docs/kb/kyc-basics#document-quality). ### Review timeline | Verification Type | Review Timeline | | ----------------- | -------------------------- | | KYC Standard | ~60 seconds | | KYC Enhanced | 3 hours to 1 business day | | KYB Standard | 3 hours to 1 business day | ### Statuses Every customer has a KYC status indicating the current state of their verification: * **`verifying`**: KYC is being processed * **`approved`**: KYC verified and approved * **`rejected`**: KYC rejected * **`compliance_request`**: compliance team opened a [Request for Information](/docs/learn/rfi) and is waiting for additional documents or clarifications; the customer is paused * **`approved_rfi`**: the customer is approved and fully operational, but has an open [Request for Information](/docs/learn/rfi#approved-with-an-open-rfi) they still need to answer On creation the status is `verifying`. The update timeline depends on the KYC type: * **KYC Standard**: after ~60 seconds the status typically becomes `approved` or `rejected` automatically. When our compliance team needs to review manually, it stays `verifying` until they decide. * **KYC Enhanced & KYB Standard**: always require manual review, so the status stays `verifying` until review completes. When KYC is rejected, BlindPay returns feedback in the `kyc_warnings` or `fraud_warnings` field explaining what to correct. To retry after a rejection, create a brand new customer with corrected information — you cannot update an existing customer's KYC information. ### Limits Transfer limits are calculated on the stablecoin amount transferred. Each customer has separate limits for payouts (sending) and payins (receiving). | | KYC Standard | KYB Standard | KYC Enhanced | | --------------- | ------------ | ------------ | ------------ | | Per transaction | US$ 10,000 | US$ 30,000 | US$ 50,000 | | Daily | US$ 50,000 | US$ 100,000 | US$ 100,000 | | Monthly | US$ 100,000 | US$ 250,000 | US$ 500,000 | **Note**: These limits are set for compliance purposes and can be increased upon submission of additional documentation. See [Limit increase](/docs/learn/limit-increase) for the full flow. ## Prerequisites ## Create a customer You can check the required fields in the [BlindPay API Docs](https://api.blindpay.com/reference#tag/customers/POST/v1/instances/{instance_id}/customers). ```bash [Standard KYC] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "tos_id": "", "type": "individual", "kyc_type": "standard", "email": "email@example.com", "tax_id": "12345678", "address_line_1": "8 The Green", "address_line_2": "#12345", "city": "Dover", "state_province_region": "DE", "country": "US", "postal_code": "02050", "ip_address": "127.0.0.1", "phone_number": "+13022006100", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/v4-460px-Get-Proof-of-Address-Step-3-Version-2.jpg.jpeg", "first_name": "John", "last_name": "Doe", "date_of_birth": "1998-01-01T00:00:00Z", "id_doc_country": "US", "id_doc_type": "PASSPORT", "id_doc_front_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/1000_F_365165797_VwQbNaD4yjWwQ6y1ENKh1xS0TXauOQvj.jpg", "selfie_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/selfie.png" }' ``` ```bash [Enhanced KYC] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "tos_id": "", "type": "individual", "kyc_type": "enhanced", "email": "email@example.com", "tax_id": "123456788", "address_line_1": "8 The Green", "address_line_2": "#12345", "city": "Dover", "state_province_region": "DE", "country": "US", "postal_code": "02050", "ip_address": "127.0.0.1", "phone_number": "+13022006100", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/v4-460px-Get-Proof-of-Address-Step-3-Version-2.jpg.jpeg", "first_name": "John", "last_name": "Doe", "date_of_birth": "1998-01-01T00:00:00Z", "id_doc_country": "US", "id_doc_type": "PASSPORT", "id_doc_front_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/1000_F_365165797_VwQbNaD4yjWwQ6y1ENKh1xS0TXauOQvj.jpg", "selfie_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/selfie.png", "source_of_funds_doc_file": "https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/source-of-funds.jpg", "source_of_funds_doc_type": "business_income", "purpose_of_transactions": "business_transactions", "purpose_of_transactions_explanation": "I am using the money for my personal expenses." }' ``` ```bash [Standard KYB] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "tos_id": "", "type": "business", "kyc_type": "standard", "email": "test@blindpay.com", "tax_id": "123456", "address_line_1": "8 The Green", "city": "Dover", "state_province_region": "DE", "country": "US", "postal_code": "19901", "phone_number": "+13022006336", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://files.blindpay.com/1767022801827-icon.png", "legal_name": "Test Inc.", "alternate_name": "Test", "formation_date": "2000-01-01T00:00:00.000Z", "website": "https://test.com", "owners": [ { "role": "beneficial_controlling", "title": "CEO", "ownership_percentage": 100, "first_name": "John", "last_name": "Doe", "date_of_birth": "2000-01-01T00:00:00.000Z", "tax_id": "GC200075", "address_line_1": "5th Avenue", "city": "Manhattan", "state_province_region": "NY", "country": "US", "postal_code": "", "id_doc_country": "US", "id_doc_type": "PASSPORT", "id_doc_front_file": "https://files.blindpay.com/1767022774904-blindpay-square.svg", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://files.blindpay.com/1767022787021-icon.png" } ], "incorporation_doc_file": "https://files.blindpay.com/1767022793017-icon.png", "proof_of_ownership_doc_file": "https://files.blindpay.com/1767022796322-icon.png" }' ``` ### Testing scenarios By default all customers created in `development` instances are automatically approved. To test a rejection, use `Fail` as the first name (individuals) or legal name (businesses). ## Related * [Wallets](/docs/wallets) · [Bank Accounts](/docs/bank-accounts) · [Blockchain Wallets](/docs/blockchain-wallets) * [KYC Basics](/docs/kb/kyc-basics) · [Requests for Information](/docs/learn/rfi) --- --- url: /docs/learn/terms-of-service.md description: >- A legal agreement your customers must accept before you create them and start KYC. --- BlindPay's terms of service is a legal agreement your customers must accept before you can create them. Acceptance is required for regulatory compliance and lets BlindPay provide services such as issuing blockchain wallets and virtual accounts on their behalf. The terms can only be accepted by a user on the client side at `https://app.blindpay.com`; requests from servers are ignored. ## How it works When a user accepts the terms, BlindPay redirects them to your `redirect_url` with a `tos_id` query parameter and emits a `tos.accept` webhook. You pass that `tos_id` when [creating a customer](/docs/learn/customers). ## Prerequisites ## Generate a terms of service URL The API only accepts a `uuid` on the `idempotency_key` field. Reusing the same key on a second request is rejected. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/e/instances/in_000000000000/tos \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "idempotency_key": "" }' ``` The response is a URL with the following query parameters: ```bash [URL example] https://app.blindpay.com/e/terms-of-service?session_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...&idempotency_key=5d8b149e-a55d-4b5b-a8f8-7c4fa315f854&redirect_url= ``` | Param | Required | Example | | --- | --- | --- | | `session_token` | Yes | JWT | | `idempotency_key` | Yes | uuid | | `redirect_url` | No | `https://yourapp.com/` | | `customer_id` | No | `re_000000000000` (required when accepting a new terms of service version) | We strongly recommend adding a `redirect_url` so the customer lands back in your application after accepting. ## Accept the terms of service Open the generated URL for the customer to accept on `https://app.blindpay.com`. After acceptance, BlindPay redirects to your `redirect_url` and appends a `tos_id` query parameter. Copy that `tos_id`: you'll pass it as `tos_id` when you [create the customer](/docs/learn/customers). You also receive a `tos.accept` webhook event when the terms of service is accepted. ## Accepting a new version Each `tos_id` is tied to the terms of service version active when it was generated, and can only ever be linked to one customer. If BlindPay updates the terms, calls to the payout quote and payin quote endpoints return an error with message `please_accept_terms_of_service` for customers whose acceptance predates the new version. Generate a new URL, set `customer_id` on it, and have the customer accept again. Once accepted, the quote endpoints stop returning the error. ## Related * [Customers](/docs/learn/customers): pass the `tos_id` when creating a customer * [Instances](/docs/learn/instances) · [API keys](/docs/learn/api-keys) * [KYC](/docs/kb/kyc): verification levels and the full onboarding flow --- --- url: /docs/learn/rfi.md description: >- Respond programmatically when BlindPay's compliance team needs additional documentation from a customer. --- ## What it is A Request for Information (RFI) is how BlindPay's compliance team asks for missing or clarifying details when a customer's KYC or KYB review is incomplete. Instead of rejecting the application, BlindPay pauses it, attaches a list of fields the customer needs to fill in, and notifies you so you can collect the response from your customer. This page covers the **API integration**. For a non-technical walkthrough, see the [Requests for Information guide](/docs/kb/information-requests). ## How it works 1. **Create a customer.** Their `kyc_status` starts as `verifying`. 2. **Listen for the `customer.update` webhook.** When compliance opens an RFI, you receive an event with `kyc_status: compliance_request`. 3. **`GET /v1/.../rfi`** to fetch the open RFI and the list of fields the customer needs to fill in. 4. **Collect the fields** from your customer through your own UI. 5. **`POST /v1/.../rfi`** to submit the response in a single shot. 6. **Listen for `customer.update` again.** The `kyc_status` flips back to `verifying` while BlindPay re-reviews. 7. **Wait for the final webhook.** The customer ends up `approved`, `rejected`, or back in `compliance_request` if compliance needs another round. While the customer is in `compliance_request`, **payouts and payins cannot be created** for them. ### KYC status The RFI statuses extend the set documented on the [Customers](/docs/learn/customers#statuses) page: * **`verifying`**: Initial review or post-RFI re-review * **`approved`**: KYC has been verified * **`rejected`**: KYC has been rejected (final) * **`compliance_request`**: An RFI is open and the customer is paused waiting for your response * **`approved_rfi`**: An RFI is open but the customer stays approved and fully operational. See [Approved with an open RFI](#approved-with-an-open-rfi). ### Approved with an open RFI Compliance can open an RFI without pausing an already-approved customer. The customer's `kyc_status` becomes `approved_rfi` and they keep full access (payouts, payins, and virtual accounts keep working) while the RFI is open. The integration is identical: the same webhook, fetch, and submit steps below apply. The differences: | | `compliance_request` | `approved_rfi` | | --- | --- | --- | | While the RFI is open | Payouts and payins are blocked | Customer keeps full access | | After the response is submitted | `verifying`, then compliance re-reviews | `approved` immediately, no re-review | | If the 27-day deadline passes | Customer is auto-rejected | Customer is **not** auto-rejected; compliance reviews the case manually | ### Deadline When an RFI is opened, the customer has **27 days** to respond. If no submission arrives within that window, BlindPay automatically rejects the customer (except customers in `approved_rfi`, who stay operational and are reviewed manually by compliance). The deadline is included as `expires_at` in the RFI payload, and every new RFI starts a fresh window. There can only be **one open RFI per customer** at a time. If compliance needs another round, a new RFI is created after the previous one is reviewed. ### RFI status The `status` field on an RFI object reflects its lifecycle: | Status | Meaning | | ------------- | ------------------------------------------------------------------------------------ | | `pending` | The RFI is open and waiting for a response. The customer is in `compliance_request` or `approved_rfi`. | | `submitted` | The response has been received. The customer is back in `verifying` (or `approved` if they were in `approved_rfi`). | | `expired` | The 27-day deadline passed without a submission. The customer was auto-rejected (`approved_rfi` customers are not auto-rejected). | | `cancelled` | Compliance cancelled the RFI. The customer was restored to its prior status. | ### Endpoints | Method | Path | Description | | ------ | --------------------------------------------------------------------- | ---------------------------------------- | | `GET` | `/v1/instances/{instance_id}/customers/{customer_id}/rfi` | Fetch the open (pending) RFI, or `404`. | | `POST` | `/v1/instances/{instance_id}/customers/{customer_id}/rfi` | Submit the response as a flat object. | ## Prerequisites ## Receive the webhook Subscribe to the [`customer.update` webhook](/docs/learn/webhooks). When a customer enters or leaves `compliance_request`, you receive a payload with the new status: ```json { "webhook_event": "customer.update", "id": "re_000000000000", "instance_id": "in_000000000000", "kyc_status": "compliance_request", "first_name": "John", "last_name": "Doe", "...": "..." } ``` ## Fetch the open RFI ```bash curl --request GET \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/rfi \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' ``` Returns the open RFI, or `404` if none is open for this customer: ```json { "id": "rfi_a1b2c3d4e5f6", "customer_id": "re_000000000000", "instance_id": "in_000000000000", "status": "pending", "request": [ { "title": "Business Description", "description": "The business description doesn't match the company's website. Please provide a clarification.", "fields": [ { "key": "business_description", "label": "Business Description", "required": true }, { "key": "business_description_explanation", "label": "Explanation", "required": true } ] }, { "title": "Proof of Address", "description": "The Proof of Address attached doesn't match the address provided. Please upload a new one.", "fields": [ { "key": "proof_of_address_doc_type", "label": "Proof of Address Type", "required": true, "items": [ { "label": "Utility Bill", "value": "UTILITY_BILL" }, { "label": "Bank Statement", "value": "BANK_STATEMENT" } ] }, { "key": "proof_of_address_doc_file", "label": "Proof of Address File", "required": true, "regex": "^https://[^\\s]+$" } ] } ], "response": {}, "expires_at": "2026-06-08T13:00:00.000Z", "submitted_at": null, "created_at": "2026-05-12T13:00:00.000Z" } ``` ### Request schema The `request` field is an array of **sections**. Each section is a self-contained question with a `title`, a `description` written by compliance, and one or more **fields** the customer must fill in. | Property | Type | Description | | --------------------- | --------- | ---------------------------------------------------------------------------- | | `title` | `string` | Section heading shown above the inputs. | | `description` | `string` | Compliance's prompt to the customer. Render verbatim. | | `supporting_document` | `string` | Optional. URL to a template or example document (e.g. a Google Drive link). | | `fields` | `Field[]` | The inputs to render and collect. | Each entry in `fields`: | Property | Type | Description | | ---------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `key` | `string` | Unique key within the RFI. **This is the key you must use in the response body.** | | `label` | `string` | The label to show above the input. | | `required` | `boolean` | If `true`, the field must be present and non-empty in the response. | | `regex` | `string` | Optional. A regex pattern that the response value must match. | | `items` | `{ label: string, value: string }[]` | Optional. If present, the field is a dropdown and the response must be one of the provided `value`s. | | `multiple` | `boolean` | Optional. If `true`, the field accepts an **array of URLs** (multiple file uploads). | For any file upload field, use the [Upload endpoint](/docs/learn/upload) to host the file and submit the resulting URL. ## Submit a response The response is a **flat object** keyed by `field.key`. There is no `rfi_id` in the URL because there's only ever one open RFI per customer. **The submission is single-shot.** All required fields must be included in one request. There is no partial save, so a submission missing required fields is rejected with `400`. ```bash curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/rfi \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "business_description": "We sell B2B SaaS payroll software to mid-market companies.", "business_description_explanation": "Updated description matches our public website.", "proof_of_address_doc_type": "UTILITY_BILL", "proof_of_address_doc_file": "https://files.blindpay.com/1767022801827-bill.pdf" }' ``` Response: ```json { "success": true } ``` ### Validation The body is validated dynamically against the stored `request.fields[]`: * `required` fields must be present and non-empty * `regex` is applied to the value as a `RegExp` test * `multiple: true` requires a `string[]` of URLs (max 20) * Unknown keys (not declared in the request) are rejected A validation failure returns `400` with details about the offending key. ### After submission Once submitted, `customer.update` fires with `kyc_status: "verifying"` while BlindPay re-reviews. After re-review you receive one of: * `kyc_status: "approved"` if the customer passed * `kyc_status: "rejected"` if the customer failed * `kyc_status: "compliance_request"` if a new RFI was opened. Repeat from [Receive the webhook](#receive-the-webhook). If the 27-day window elapses without a submission, `customer.update` fires with `kyc_status: "rejected"` for the auto-rejection. For a customer in `approved_rfi`, the submission restores them directly: `customer.update` fires with `kyc_status: "approved"` and there is no re-review step. Missing the deadline does not auto-reject them. ## Related * [Customers](/docs/learn/customers#statuses) · [Webhooks](/docs/learn/webhooks) · [Upload](/docs/learn/upload) * [Requests for Information guide](/docs/kb/information-requests) --- --- url: /docs/learn/limit-increase.md description: >- Request higher per-transaction, daily, or monthly transfer limits for a customer by submitting a supporting document, and track the request through compliance review. --- Every customer starts with transfer limits set by their KYC tier. When a customer needs to move more than their tier allows, request a limit increase: you submit the new limits you want plus a supporting document, BlindPay's compliance team reviews it, and the approved limits (which may differ from what you asked for) take effect on approval. ## Default limits Transfer limits are calculated on the stablecoin amount transferred. Each customer has separate limits for payouts (sending) and payins (receiving). | | KYC Standard | KYB Standard | KYC Enhanced | | --------------- | ------------ | ------------ | ------------ | | Per transaction | US$ 10,000 | US$ 30,000 | US$ 50,000 | | Daily | US$ 50,000 | US$ 100,000 | US$ 100,000 | | Monthly | US$ 100,000 | US$ 250,000 | US$ 500,000 | ## Prerequisites The customer must exist and have `kyc_status` of `approved`; you cannot request a limit increase for a customer still in KYC review. You also need the supporting document already hosted at a URL, for example via the [Upload](/docs/learn/upload) endpoint. ## Supporting document types Pick the `supporting_document_type` that matches the customer type and the document you are submitting: | Customer type | Accepted values | | --- | --- | | Individual | `individual_bank_statement`, `individual_tax_return`, `individual_proof_of_income`, `individual_pay_stub`, `individual_employment_letter`, `individual_investment_statement`, `individual_crypto_exchange_statement`, `individual_blockchain_wallet_statement` | | Business | `business_bank_statement`, `business_financial_statements`, `business_tax_return`, `business_contract`, `business_accounts_receivable`, `business_merchant_processor_statement`, `business_shareholder_loan` | See the [knowledge base source of funds guide](/docs/kb/source-of-funds) for what compliance expects each document to show. ## Request a limit increase Amounts are in USD cents: `100000` means US$ 1,000.00. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/limit-increase \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "per_transaction": 100000, "daily": 200000, "monthly": 1000000, "supporting_document_type": "individual_bank_statement", "supporting_document_file": "https://example.com/document.pdf" }' ``` The response returns the request's ID: ```json { "id": "rl_000000000000" } ``` ## Track the request The same path lists every limit increase request for the customer, newest state included: ```bash [cURL] curl --request GET \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/limit-increase \ --header 'Authorization: Bearer YOUR_API_KEY' ``` Each entry carries the requested amounts, the review status, and, once reviewed, the approved amounts: | Field | Meaning | | --- | --- | | `status` | `in_review`, `approved`, or `rejected` | | `per_transaction`, `daily`, `monthly` | The amounts you requested, in USD cents | | `approved_per_transaction`, `approved_daily`, `approved_monthly` | What compliance actually granted; can be lower than requested. `null` until reviewed | | `supporting_document_type`, `supporting_document_file` | The document submitted with the request | Compliance can approve lower limits than requested. Always read the `approved_*` fields rather than assuming the requested amounts were granted. ## Webhooks | Event | Fires when | | --- | --- | | `limitIncrease.new` | A limit increase request is created | | `limitIncrease.update` | Compliance approves or rejects the request | See [Webhooks](/docs/learn/webhooks) for signature verification and delivery details. ## Related * [Customers](/docs/learn/customers): KYC tiers and the default limits * [Upload](/docs/learn/upload): host the supporting document * [Knowledge base: source of funds](/docs/kb/source-of-funds): what each supporting document must show --- --- url: /docs/learn/upload.md description: Generate encrypted file URLs from customer KYC documents and pictures. --- ## What it is Upload generates file URLs from your customers' KYC documents and pictures. BlindPay encrypts them before sharing with vendors and saving them in our database, helping you stay compliant with data protection laws. ## Prerequisites ## Upload a file You can check the required fields in the [BlindPay API Docs](https://api.blindpay.com/reference#tag/upload/POST/v1/upload). ```bash [cURL] curl 'http://localhost:8787/v1/upload?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'bucket=onboarding' \ --form 'file=your_file.pdf' # this file should be a standard File format, please see more: https://developer.mozilla.org/en-US/docs/Web/API/File ``` Success! Use the `file_url` returned by the API to populate the required document URLs when [creating a customer](/docs/learn/customers#create-a-customer). ## Open an uploaded file Files are stored in a private bucket, so the `file_url` returned by the upload is not directly downloadable. To open a file, exchange its `file_url` for a presigned URL: a temporary link that grants read access for 1 hour. You can check the required fields in the [BlindPay API Docs](https://api.blindpay.com/reference#tag/upload/POST/v1/presign). ```bash [cURL] curl 'http://localhost:8787/v1/presign?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --data '{ "file_url": "https://files.blindpay.com/1712345678901-document.pdf" }' ``` The response contains the temporary link and when it stops working: ```json { "file_url": "https://files.blindpay.com/1712345678901-document.pdf?Expires=...&Signature=...&Key-Pair-Id=...", "expires_at": "2026-01-01T13:00:00.000Z" } ``` * `file_url` must be a BlindPay file URL returned by the upload endpoint. Any other URL is rejected with `file_url_invalid`. * You can only presign files that were uploaded with your `instance_id`. Requesting another instance's file returns `403`. * The presigned URL expires after 1 hour and this is not configurable. Request a new one whenever you need to open the file again; do not store it. ## Related * [Analyze Document](/docs/learn/analyze-document) · [Customers](/docs/learn/customers) · [KYC Basics](/docs/kb/kyc-basics) --- --- url: /docs/learn/analyze-document.md description: >- Read a KYC document with AI and get an approval-rate signal before you submit it. --- ## What it is Analyze Document reads a PDF, JPG, or PNG with AI, checks it against the rules for its document type, and returns an `approval_rate` of `low`, `medium`, or `high` plus a short reason. Use it to pre-screen customer documents before submitting them for KYC, so you can prompt for a better file early instead of waiting for a rejection. The analysis is a signal, not a decision. It does not approve or reject KYC; the official outcome still comes from the verification flow. ## Prerequisites ## Document types Pass the `type` that matches the document. It selects the rule set used for the analysis. | Type | Use for | | ------------------------ | ---------------------------------------------------------- | | `identity_document` | National ID, driver's license, or residence permit | | `passport` | Passport | | `selfie` | Selfie / liveness photo | | `proof_of_address` | Utility bill, bank statement, or lease as proof of address | | `incorporation_document` | Company incorporation / registration document | | `proof_of_ownership` | Proof of company ownership | | `source_of_funds` | Source of funds evidence | | `proof_of_income` | Proof of income | | `bank_statement` | Bank statement | | `financial_statement` | Company financial statement | | `tax_return` | Tax return | | `invoice` | Invoice | | `transaction_document` | Transaction supporting document | ## Optional metadata Pass `metadata` as a JSON string of values you already collected to cross-check them against the document. A clear mismatch caps the rating at `low` and the reason explains it. | Field | Checked against | | --------------------------- | ------------------------------------------------------- | | `full_name` | Name on the document | | `entity_name` | Company name on the document | | `date_of_birth` | Date of birth on the document | | `address` | Address on the document | | `id_number` | ID / document number | | `requested_per_transaction` | Capacity shown supports the per-transaction limit (USD) | | `requested_daily` | Capacity shown supports the daily limit (USD) | | `requested_monthly` | Capacity shown supports the monthly limit (USD) | Only send fields you have already collected. Name, entity, date of birth, address, and ID are matched against the document; requested limits are checked for sufficient capacity, with non-USD amounts converted at a reasonable rate. ## Analyze a document You can check the required fields in the [BlindPay API Docs](https://api.blindpay.com/reference#tag/upload/POST/v1/upload/analyze). The file must be a PDF, JPG, or PNG of up to 5MB. The real format is detected from the file contents, so a mislabeled extension is rejected. Each tab shows the `type` with the metadata fields that fit it. The file should be a standard [File](https://developer.mozilla.org/en-US/docs/Web/API/File) format. ```bash [Identity document] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=identity_document' \ --form 'metadata={"full_name":"Jane Doe","date_of_birth":"1990-01-01","id_number":"123456789"}' \ --form 'file=your_file.pdf' ``` ```bash [Passport] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=passport' \ --form 'metadata={"full_name":"Jane Doe","date_of_birth":"1990-01-01","id_number":"A12345678"}' \ --form 'file=your_file.pdf' ``` ```bash [Selfie] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=selfie' \ --form 'metadata={"full_name":"Jane Doe"}' \ --form 'file=your_file.jpg' ``` ```bash [Proof of address] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=proof_of_address' \ --form 'metadata={"full_name":"Jane Doe","address":"123 Main St, Miami, FL 33101"}' \ --form 'file=your_file.pdf' ``` ```bash [Incorporation document] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=incorporation_document' \ --form 'metadata={"entity_name":"Acme Inc","id_number":"12-3456789"}' \ --form 'file=your_file.pdf' ``` ```bash [Proof of ownership] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=proof_of_ownership' \ --form 'metadata={"entity_name":"Acme Inc","full_name":"Jane Doe"}' \ --form 'file=your_file.pdf' ``` ```bash [Source of funds] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=source_of_funds' \ --form 'metadata={"full_name":"Jane Doe","requested_monthly":"50000"}' \ --form 'file=your_file.pdf' ``` ```bash [Proof of income] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=proof_of_income' \ --form 'metadata={"full_name":"Jane Doe","requested_monthly":"20000"}' \ --form 'file=your_file.pdf' ``` ```bash [Bank statement] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=bank_statement' \ --form 'metadata={"full_name":"Jane Doe","requested_monthly":"30000"}' \ --form 'file=your_file.pdf' ``` ```bash [Financial statement] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=financial_statement' \ --form 'metadata={"entity_name":"Acme Inc","requested_monthly":"500000"}' \ --form 'file=your_file.pdf' ``` ```bash [Tax return] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=tax_return' \ --form 'metadata={"full_name":"Jane Doe","id_number":"123-45-6789"}' \ --form 'file=your_file.pdf' ``` ```bash [Invoice] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=invoice' \ --form 'metadata={"entity_name":"Acme Inc","requested_per_transaction":"10000"}' \ --form 'file=your_file.pdf' ``` ```bash [Transaction document] curl 'http://localhost:8787/v1/upload/analyze?instance_id=in_000000000000' \ --request POST \ --header 'Content-Type: multipart/form-data' \ --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \ --form 'type=transaction_document' \ --form 'metadata={"entity_name":"Acme Inc","requested_per_transaction":"25000"}' \ --form 'file=your_file.pdf' ``` The response tells you how confident the analysis is and why: ```json { "approval_rate": "high", "description": "Recent utility bill issued within the last 3 months, name and full address clearly legible." } ``` ## Related * [Upload](/docs/learn/upload) · [Customers](/docs/learn/customers) · [KYC Basics](/docs/kb/kyc-basics) --- --- url: /docs/virtual-accounts.md description: >- Issue a virtual account (virtual bank account) that receives USD bank transfers and settles automatically to stablecoins like USDC or USDT in your customer's wallet. --- Your customer gets a real US bank account in their own name, with its own routing and account number. Anyone can send money to it like to any other bank account. Each deposit creates a [payin](/docs/payins). BlindPay converts the deposit and sends USDC or USDT (USDB on development instances) to the wallet you linked at creation. You do not build a checkout flow or show payment instructions. The account number is the integration. How BlindPay fees are charged depends on the deposit amount: deposits below $100.00 accrue the fee to your invoice at the end of the billing cycle (`billing_fee_amount` on the payin), while deposits of $100.00 or more are charged at the time of the transaction, deducted from the amount delivered on-chain (`transaction_fee_amount`). See [Fees on deposits](#fees-on-deposits). ## How it works Each virtual account belongs to one customer and settles to one blockchain wallet. A customer can hold more than one account. Give the routing and account number to the payer. Track each deposit through the payin it creates. ### Available account types | Type | Payment methods | Countries | SLA | Cost | | --- | --- | --- | --- | --- | | US (individuals and businesses) | ACH, Wire, SWIFT | US and foreign | 24 hours | $1.50/mo per account | | US (businesses, foreign senders) | ACH, RTP, Wire, SWIFT | Foreign only | 3-5 business days | $1.50/mo per account | | US (businesses, US senders) | ACH, Wire, SWIFT | US only | 3-5 business days | $1.50/mo per account | Named payouts: when a virtual account is issued to an end user, all payouts originating from that account are sent under the end user's name rather than a pooled or omnibus account name. This ensures the recipient sees the actual payer on incoming transfers and aligns with originator-information requirements for payment-rail compliance. These accounts can also send and collect third-party payments. ### Fees on deposits Every deposit is processed regardless of size, including $0.01 account-verification micro-deposits, and always delivers at least $0.01 of stablecoin to the linked wallet. Where the BlindPay fee lands depends on the deposit amount: | Deposit amount | BlindPay fee | Field on the payin | | --- | --- | --- | | Below $100.00 | Accrued to your invoice, charged at the end of the billing cycle | `billing_fee_amount` | | $100.00 or more | Charged at the time of the transaction, deducted from the delivered amount | `transaction_fee_amount` | Instances configured for end-of-month billing accrue the fee to `billing_fee_amount` for every deposit, regardless of amount. If a [partner fee](/docs/learn/partner-fees) applies to the account, it is collected from what remains after any transaction-time deduction, capped so the deposit always delivers at least $0.01. On small deposits this can mean partial collection (for example, a $5.00 fee on a $5.00 deposit collects $4.99) or none at all on micro-deposits. ### Approval process After a virtual account is created via the API, it goes through a two-stage review: 1. **Compliance review.** The virtual account starts in `pending_review` status while BlindPay's compliance team reviews the application. 2. **Bank review.** Once compliance approves, the account moves to `verifying` while BlindPay's banking partner does final approval. Issuance SLAs vary by account type, see the table above. Approval is not guaranteed at either stage: both BlindPay's compliance team and the banking partner reserve the right to reject an application. On a development instance, virtual accounts skip both stages and are created directly in `approved` status. ### Statuses | Status | Description | | --- | --- | | `pending_review` | The virtual account has been created and is awaiting compliance review. | | `verifying` | Compliance approved. The virtual account has been submitted to the bank. | | `approved` | The bank has approved the virtual account. It is now active, with real routing and account numbers, and the `virtualAccount.complete` webhook has fired. | | `rejected` | The virtual account was rejected during compliance or bank review. | A customer can't have two non-deleted virtual accounts of the same type unless the earlier one was `rejected`. Create a new customer, or wait for the rejection, before retrying that type. ## Frequently asked questions **What is a stablecoin virtual account?** A dedicated bank account issued in your customer's name whose incoming bank deposits (ACH, wire, or SWIFT) are automatically converted to stablecoins like USDC or USDT and delivered to a linked wallet. **How do I create a virtual account for USDC?** Create a customer that has completed KYC, link a wallet as the settlement destination, and call the virtual accounts API. See [Create a virtual account](/docs/virtual-accounts-create) for the full request. **Can a virtual account receive international payments?** Yes. US virtual accounts accept ACH, wire, and SWIFT transfers from US and foreign senders, depending on the account type. **How much does a stablecoin virtual account cost?** US virtual accounts cost $1.50 per month per account. Deposits generate payins; BlindPay fees on deposits below $100.00 are charged on your monthly invoice, and deposits of $100.00 or more are charged at the time of the transaction. ## Next * [Create a virtual account](/docs/virtual-accounts-create): prerequisites, required fields, the create request, and webhooks. ## Related * [Bank transfer in](/docs/payins) * [Blockchain wallets](/docs/blockchain-wallets) - [Offramp wallets](/docs/offramp-wallets): the mirror of a virtual account, a deposit address whose incoming stablecoin pays out as fiat * [Knowledge base: virtual account requirements](/docs/kb/virtual-accounts) * [Learn: webhooks](/docs/learn/webhooks) --- --- url: /docs/virtual-accounts-create.md description: >- Create a virtual account with the BlindPay API, from customer prerequisites and required compliance fields to the webhooks that confirm approval. --- The request is one API call. The work is in the setup. The customer needs approved KYC, the compliance fields the banking partner checks, and a linked wallet for settlement. This page goes through each step in order and ends with the webhooks that tell you the account is live. ## Prerequisites You must also [create a customer](/docs/quickstart-payin) and add a [blockchain wallet](/docs/blockchain-wallets) before generating a virtual account. The customer's `kyc_status` must already be `approved`: you cannot create a virtual account for a customer still in KYC review. The wallet is where stablecoin settlement lands behind the scenes; see [Blockchain wallets](/docs/blockchain-wallets) for the full mechanics. ## Required fields ### For businesses Make sure the following fields are filled in on the customer before requesting a virtual account: * `account_purpose`, `business_type`, `business_description`, `business_industry` (NAICS code), `estimated_annual_revenue`, `source_of_wealth`, `publicly_traded` * At least one registered business owner, with `ownership_percentage` and `title` * Owners living in the US must have their SSN in `tax_id` Depending on the banking partner, the API rejects the creation request with a `missing_required_fields` error naming each blank field, so fill these in before calling the endpoint rather than relying on manual review to catch them. You can find the `business_industry` NAICS code list at api.blindpay.com/reference. To update these fields on an existing customer: ```bash [cURL] curl --request PUT \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000 \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "email": "john@doe.com", "tax_id": "000000000", "country": "US", "owners": [ { "ownership_percentage": 50, "title": "CTO", "id": "ub_000000000000" } ], "account_purpose": "business_expenses", "business_type": "corporation", "business_description": "description", "business_industry": "541511", "estimated_annual_revenue": "0_99999", "source_of_wealth": "business_dividends_or_profits", "publicly_traded": false }' ``` ### For individuals (sole proprietors) Individual customers need `account_purpose` and `source_of_wealth` filled in on the customer, subject to the same `missing_required_fields` rejection as businesses. Sole proprietors must also include a supporting document in the virtual account creation request itself: * `sole_proprietor_doc_type`: type of supporting document, one of `master_service_agreement`, `salary_slip`, `bank_statement` * `sole_proprietor_doc_file`: a URL pointing to the uploaded document ## The request After creation, a production virtual account starts in `pending_review` status. It moves through compliance and bank review before becoming `approved`. You'll get a webhook notification at each transition. ```bash [Business] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/virtual-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "banking_partner": "cfsb", "token": "USDC", "blockchain_wallet_id": "bw_000000000000", "partner_fee_id": "pf_000000000000" }' ``` ```bash [Sole proprietor] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/virtual-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "banking_partner": "cfsb", "token": "USDC", "blockchain_wallet_id": "bw_000000000000", "sole_proprietor_doc_type": "master_service_agreement", "sole_proprietor_doc_file": "https://example.com/document.pdf" }' ``` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `banking_partner` | string | Yes | The example value is `cfsb`. Available values depend on your instance and region. | | `token` | `USDC`, `USDT`, `USDB` | Yes | Settlement stablecoin. `USDT` requires the linked wallet's network to be Polygon, Ethereum, or Solana. `USDB` is development-only. | | `blockchain_wallet_id` | string (`bw_...`) | Yes | Must belong to the same customer. | | `sole_proprietor_doc_type` | `master_service_agreement`, `salary_slip`, `bank_statement` | Conditional | Required for individual customers on this banking partner. | | `sole_proprietor_doc_file` | URL string | Conditional | Required alongside `sole_proprietor_doc_type`. | | `partner_fee_id` | string (`pf_...`) | No | Pins a [partner fee](/docs/learn/partner-fees) to this account's automated deposits, overriding the instance-wide virtual accounts default fee. Omit to use the default. | The response includes `id` (`va_...`), `banking_partner`, `kyc_status`, `token`, `blockchain_wallet_id`, `partner_fee_id`, and a `us` object with `ach`, `wire`, and `rtp` sub-objects (each `{routing_number, account_number}`), plus SWIFT and receiving-bank details once the account is `approved`. Before approval, rail numbers are empty. You can update `token`, `blockchain_wallet_id`, and `partner_fee_id` on an existing virtual account (`partner_fee_id: null` clears the pin back to the instance default); `banking_partner` cannot be changed after creation. ## Webhooks | Event | Fires when | | --- | --- | | `virtualAccount.new` | A virtual account is successfully created (every status path, including development). | | `virtualAccount.complete` | The banking partner confirms the account and `kyc_status` flips to `approved`. Rail numbers are populated in the payload. | See [Webhooks](/docs/learn/webhooks) for signature verification and delivery details. ## Related * [Virtual accounts](/docs/virtual-accounts): what it is, account types, and statuses * [Bank transfer in](/docs/payins): how deposits into the account become payins * [Blockchain wallets](/docs/blockchain-wallets): where settlement lands * [Knowledge base: virtual account requirements](/docs/kb/virtual-accounts): source of funds and source of wealth documents --- --- url: /docs/store.md description: >- Hold a customer's balance in a BlindPay-managed wallet, or register an external wallet the customer already controls. --- Once a customer receives money, it needs somewhere to live before it goes back out. A **managed wallet** is a balance BlindPay creates and holds in the customer's name: deposits land in it, payouts are funded from it, and you check it with a single API call. There are no keys to manage, no apps to connect, and no extra infrastructure on your side. Once your customer holds stablecoins, they need somewhere to keep them. BlindPay supports two options, and you can mix both across different customers or even for the same customer. A **managed wallet** (`bl_...`) is a BlindPay-custodied wallet: BlindPay creates it, holds the private keys, and your customer interacts with the balance through your product rather than a browser extension or a signed transaction. An **external blockchain wallet** (`bw_...`) is the opposite: your customer already controls the wallet (self-custodied), and you only register its address with BlindPay so it can receive stablecoin transfers or authorize a payout. Managed wallets are in beta: only the chains listed on the managed wallet page have confirmed support. ## Which to use | | Managed wallet (`bl_`) | Blockchain wallet (`bw_`) | | --- | --- | --- | | Custody | BlindPay-custodied | Customer-controlled (non-custodial) | | Address | Generated by BlindPay | Supplied or signed by the customer | | Best for | Products where you don't want customers managing keys | Customers who already have their own wallet | | Sending funds | No client-side signing | Requires signature, approval, or delegation depending on chain | | Availability | Beta, limited chain support | Generally available | ## How it fits 1. A [payin](/docs/payins) or a [virtual account](/docs/virtual-accounts) deposit settles into the customer's managed wallet. 2. The balance sits there until you move it; check it any time with the balance endpoint. 3. A [payout](/docs/payouts) pulls from the wallet and delivers to a bank account, one API call, no extra approval step. You only ever reference the wallet by its ID (`bl_...`) on payin quotes, or by its address when funding a payout. Everything else is handled by BlindPay. ## In this section * [Managed wallets](/docs/wallets): create a wallet, check its balance, and use it on payins and payouts. ## Related * [Virtual accounts](/docs/virtual-accounts): a dedicated bank account whose deposits settle into the linked wallet * [Payins](/docs/payins): accept a bank transfer and credit the wallet * [Payouts](/docs/payouts): pay out from the wallet to a bank account * [Webhooks](/docs/learn/webhooks): get notified the moment funds land ## In this section * [Managed wallet](/docs/wallets): create a `bl_` wallet, check its balance, and receive stablecoins directly into it. * [Mint USDB](/docs/mint-usdb): fund a wallet with BlindPay's test stablecoin on development instances. Blockchain wallets (`bw_`) are documented under [Payins](/docs/blockchain-wallets), where they serve as the customer-controlled delivery target. ## Related * [Receive](/docs/receive): stablecoins arriving in a managed wallet * [Send](/docs/send): move stablecoins out of a managed wallet * [Payins](/docs/payins): accept fiat and credit a wallet * [Payouts](/docs/payouts): pay out fiat from a wallet's balance * [Supported chains](/docs/kb/supported-chains): chain and token matrix --- --- url: /docs/wallets.md description: >- Create a BlindPay-managed wallet, check its balance, and use it on payins and payouts. --- A managed wallet (`bl_...`) is a balance BlindPay creates and manages for a customer. You interact with it in three ways: create it once, check its balance, and reference it on payins and payouts. The settlement machinery behind it is BlindPay's problem, not yours. Managed wallets are in beta. A managed wallet is a BlindPay-custodied wallet: BlindPay generates the address, holds the keys, and your customer's balance moves through your product rather than a browser extension or a signed transaction. This page is the full reference for the managed wallet entity (`bl_...`). For the customer-controlled alternative, see [blockchain wallets](/docs/blockchain-wallets). Managed wallets are in beta. Only the chains and tokens listed below have confirmed support, don't rely on any network not in the table. ## Supported chains and tokens | Chain | Development | Production | Tokens | | --- | --- | --- | --- | | Ethereum | `sepolia` | `ethereum` | USDC, USDT | | Base | `base_sepolia` | `base` | USDC, USDT | | Polygon | `polygon_amoy` | `polygon` | USDC, USDT | | Arbitrum | `arbitrum_sepolia` | `arbitrum` | USDC, USDT | | Solana | `solana_devnet` | `solana` | USDC, USDT | `USDB` is also available on every development network above, it's BlindPay's test stablecoin and only exists on development instances. Stellar and Tron are not currently supported for managed wallets. Create a [blockchain wallet](/docs/blockchain-wallets) instead if you need those chains. ## Prerequisites A customer must exist and have `kyc_status: "approved"` before you can create a wallet for them. A customer can hold up to 10 wallets. ## Create a managed wallet ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "network": "base", "name": "Customer Balance" }' ``` `network` is the settlement rail BlindPay uses under the hood. Use `base` in production and `base_sepolia` on development instances unless you have a reason to pick another; the full list is on the Advanced flavor of this page. Save two things from the response: the `id` (`bl_...`), which payin quotes reference, and the `address`, which funds payouts. ```json { "id": "bl_000000000000", "network": "base", "address": "0x1234567890abcdef1234567890abcdef12345678", "name": "Customer Balance", "created_at": "2026-01-01T00:00:00.000Z" } ``` ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "network": "polygon", "external_id": "your-database-id", "name": "Blindpay Wallet" }' ``` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `network` | string | Yes | One of the chains in the table above | | `name` | string | Yes | Max 255 characters | | `external_id` | string | No | Your own correlation ID, max 255 characters | BlindPay generates the address server-side, you never supply one. Creating a wallet fires a `wallet.new` webhook with the same payload as the GET response. The response returns the wallet, including its `id` (`bl_...`) and `address`: ```json { "id": "bl_000000000000", "network": "polygon", "address": "0x1234567890abcdef1234567890abcdef12345678", "name": "Blindpay Wallet", "external_id": "your-database-id", "created_at": "2026-01-01T00:00:00.000Z" } ``` ### Get a managed wallet ```bash [cURL] curl --request GET \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets/bl_000000000000 \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' ``` Easy to mix up: `bl_` is a managed wallet, `bw_` is a blockchain wallet. The two prefixes don't share an obvious naming split, so double check which one you're passing into a quote or payout. ## Check the balance ```bash [cURL] curl --request GET \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets/bl_000000000000/balance \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ## Receive payments into it Pass the wallet's `id` as `wallet_id` on a [payin quote](/docs/payin-quotes) and the deposit settles into the wallet once the payin completes. Two webhooks confirm it: `payin.complete` and `wallet.inbound`. [Virtual account](/docs/virtual-accounts) deposits settle into their linked wallet the same way. ## Pay out from it Pass the wallet's `address` as `sender_wallet_address` when you [execute a payout](/docs/payouts). Because BlindPay manages the wallet, the payout is a single call, no extra authorization step. ## Related * [Store](/docs/store): how the wallet fits between payins and payouts * [Payins](/docs/payins): accept a bank transfer and credit the wallet * [Payouts](/docs/payouts): pay out from the wallet to a bank account * [Webhooks](/docs/learn/webhooks): `wallet.inbound` and the payment lifecycle events ## Receive stablecoins Share the wallet `address` with anyone sending stablecoins from an external wallet. There is no approval or signature step on the receiving end, any transfer to that address that arrives on the matching network credits the wallet. Every time stablecoins land in the wallet, a `wallet.inbound` webhook fires with this payload: ```json [wallet.inbound] { "id": "bl_000000000000", "address": "0x1234567890abcdef1234567890abcdef12345678", "network": "base", "token": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "id": "usdc", "symbol": "USDC", "amount": 100 } } ``` `wallet.inbound` reports `amount` scaled by 100 (so `100` means $1.00), while the wallet balance endpoint reports the raw amount. Don't assume the two use the same unit. `wallet.inbound` only fires for USDC and USDT deposits. See [webhooks](/docs/learn/webhooks) for signature verification. ## Collect fiat You can collect a fiat payment and have the equivalent stablecoins delivered straight into a managed wallet. This is the same on-ramp flow as any payin, with one difference: pass `wallet_id` instead of `blockchain_wallet_id` when creating the payin quote. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "wallet_id": "bl_000000000000", "currency_type": "sender", "cover_fees": true, "request_amount": 1000, "payment_method": "pix", "token": "USDC" }' ``` Continue with `POST /payins/evm` using the resulting `payin_quote_id`. Stablecoins land in the managed wallet's balance and a `wallet.inbound` webhook fires alongside `payin.complete`. See [Payin with managed wallet](/docs/payin-managed-wallet) for the step-by-step flow and [payins](/docs/payins) for payment methods and statuses. ## Send fiat You can fund a payout from a managed wallet's balance instead of an external wallet. Create a payout quote, then pass the managed wallet's address as `sender_wallet_address` when you execute the payout. Because BlindPay already custodies the wallet, there is no `approve` call or signature step, the payout executes directly. [Payout with managed wallet](/docs/payout-managed-wallet) walks through the full flow. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "0x1234567890abcdef1234567890abcdef12345678" }' ``` See [Payout with managed wallet](/docs/payout-managed-wallet) for the step-by-step flow, and [payouts](/docs/payouts) for statuses and testing amounts. ## Send stablecoins To move stablecoins out of a managed wallet to another managed wallet or an external address, create a transfer quote and execute it before the quote expires. See [Send](/docs/send) for the full request and response fields. Transfers are in beta. USDC transfers can move across Ethereum, Polygon, Base, and Arbitrum using Circle CCTP v2; every other token still requires the same network on both sides. The transfer quote expires in 15 seconds, the shortest of any BlindPay quote. ## Related * [Blockchain wallets](/docs/blockchain-wallets): the customer-controlled alternative to a managed wallet * [Payins](/docs/payins): collect fiat and deliver stablecoins to a wallet * [Payouts](/docs/payouts): convert a wallet's stablecoin balance to fiat * [Send](/docs/send): move stablecoins between wallets * [Webhooks](/docs/learn/webhooks): `wallet.new`, `wallet.inbound` --- --- url: /docs/payouts.md description: >- Execute a payout to a recipient's bank account and track it from processing to completed, failed, or refunded. --- Send covers paying fiat out to a recipient's bank account. As a bank-rails integrator you settle in stablecoins under the hood, but you only need to think in three steps: add a bank account, create a payout quote, and execute the payout. ## How it works Every payout follows the same three-step pattern: 1. Add a bank account for the recipient (once per recipient) 2. Create a payout quote: locks the exchange rate and fees for 5 minutes 3. Execute the payout: moves funds from the funding source to the bank account ### Supported payout rails | `type` | Country | | --- | --- | | `international_swift` | Global | | `ach` | United States | | `wire` | United States | | `rtp` | United States | | `pix` | Brazil | | `spei_bitso` | Mexico | | `ach_cop_bitso` | Colombia | | `transfers_bitso` | Argentina | | `sepa` | Europe (SEPA zone) | You can add third-party bank accounts: a customer named "John" can have a payout sent to a bank account belonging to "Jack". ### Funding source Every payout needs a funding source, the wallet the settlement stablecoins are pulled from. * **BlindPay-managed wallet balance**: the simple path. A managed wallet is custodied by BlindPay on the customer's behalf, so there is no client-side signing. Pass its address as `sender_wallet_address` and BlindPay moves the funds directly. * **External wallet**: if the funds live in a customer-controlled wallet instead, you must authorize the transfer on-chain first before calling the payout endpoint. See the stablecoin tutorials for the full mechanics: [EVM approve](/docs/payout-evm), [Stellar authorize-and-sign](/docs/payout-stellar), or [Solana delegation](/docs/payout-solana). A payout executes the transfer locked in by a [payout quote](/docs/payout-quotes): stablecoins move out of the funding source and fiat lands in the recipient's [bank account](/docs/bank-accounts). As a bank-rails integrator, you call one endpoint and then track status; the settlement leg is plumbing. The payout endpoint is the same regardless of funding source. What differs is whether you complete an on-chain authorization step first. A payout executes the transfer locked in by a [payout quote](/docs/payout-quotes): stablecoins move out of a funding source and the equivalent fiat lands in a customer's [bank account](/docs/bank-accounts). This page is the payout reference: how funding paths differ, the response shape, statuses, testing, and webhooks. The step-by-step flows live in the per-path tutorials below. ## How it works The pattern is always the same: create a [payout quote](/docs/payout-quotes), authorize the tokens for the chosen funding path, then create the payout before the quote expires (5 minutes by default). What changes is the authorization step: | Funding source | Authorization | Tutorial | | --- | --- | --- | | [Managed wallet](/docs/wallets) (`bl_...`) | None, BlindPay custodies the balance | [Payout with managed wallet](/docs/payout-managed-wallet) | | Blockchain wallet on EVM (Ethereum, Base, Polygon, Arbitrum) | ERC-20 `approve` on the token contract, scoped to the quoted amount | [Payout with EVM](/docs/payout-evm) | | Blockchain wallet on Stellar | Call the authorize endpoint for an unsigned XDR transaction, sign it, then create the payout | [Payout with Stellar](/docs/payout-stellar) | | Blockchain wallet on Solana | Prepare a token delegation transaction, sign and submit it, then create the payout | [Payout with Solana](/docs/payout-solana) | On development instances, use the test networks (`base_sepolia`, `stellar_testnet`, `solana_devnet`) and the `USDB` test token; see [Mint USDB](/docs/mint-usdb). Production instances use the matching mainnet and `USDC` or `USDT`. ## Prerequisites You also need a [customer](/docs/overview) who has completed KYC, an [approved bank account](/docs/bank-accounts), and an unexpired [payout quote](/docs/payout-quotes). ## Execute a payout Check the required fields in the [BlindPay API Docs](https://api.blindpay.com/reference#tag/payouts/POST/v1/instances/{instance_id}/payouts/evm){target="\_blank"}. Once the funding path is authorized, one call creates the payout. EVM, Solana, and managed-wallet payouts all use `/payouts/evm`; only Stellar has its own endpoint (`/payouts/stellar`, which also takes the signed transaction). ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "YOUR_WALLET_ADDRESS" }' ``` ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ quote_id: 'qu_000000000000', sender_wallet_address: 'YOUR_WALLET_ADDRESS', }), } ) const payout = await response.json() ``` Payout creation is split by destination network, each with its own endpoint and request shape: `/payouts/evm`, `/payouts/stellar`, and `/payouts/solana`. Which one you call depends on the recipient's network, not on the funding source; see [Payout with Stellar](/docs/payout-stellar) and [Payout with Solana](/docs/payout-solana) for those request shapes. `quote_id` can only back one payout. A second call with the same quote returns an error; create a new quote instead. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "YOUR_WALLET_ADDRESS" }' ``` `quote_id` can only back one payout; a second call with the same quote fails, create a new quote instead. ### Response ```json { "id": "po_000000000000", "status": "processing", "sender_wallet_address": "0x...", "customer_id": "re_000000000000", "bank_account_id": "ba_000000000000", "offramp_wallet_id": null, "billing_fee_amount": null, "partner_fee": 0, "tracking_complete": { "step": "processing" }, "tracking_payment": { "step": "processing" }, "tracking_transaction": { "step": "processing" }, "tracking_partner_fee": { "step": "on_hold" }, "tracking_liquidity": { "step": "processing" } } ``` | Field | Type | Notes | | --- | --- | --- | | `id` | string | The payout ID (`po_...`). | | `status` | string | See status lifecycle below. | | `sender_wallet_address` | string | The funding source address the crypto was pulled from. | | `customer_id` | string | The customer this payout belongs to (`re_...`). | | `bank_account_id` | string | The recipient bank account (`ba_...`). | | `partner_fee` | number | Nonzero only if the quote referenced a `partner_fee_id`. See [partner fees](/docs/learn/partner-fees). | | `tracking_complete`, `tracking_payment`, `tracking_transaction`, `tracking_partner_fee`, `tracking_liquidity` | object | Sub-status objects with a `step` of `processing`, `on_hold`, `pending_review`, or `completed`. Poll these or rely on webhooks for progress detail beyond the top-level `status`. | ## cover\_fees `cover_fees` on the payout quote decides who absorbs the fee, and it's locked in before you authorize the tokens: | `cover_fees` | Who pays | Effect | | --- | --- | --- | | `false` | Customer | Fees are deducted from the fiat the bank account receives (the common case) | | `true` | Sender | Fees are added on top, sent as extra stablecoin, so the recipient gets the full quoted amount | See [payout quotes](/docs/payout-quotes) for the full request and response fields. ## Status lifecycle | Status | Meaning | | --- | --- | | `processing` | Default on creation. The stablecoins are being pulled from the funding source and the fiat transfer is in flight. | | `on_hold` | Held for review. All SWIFT payouts start here. ACH, wire, and RTP payouts also pass through `on_hold` as a standard step, for compliance review after the crypto is collected. | | `completed` | The fiat landed in the recipient's bank account. Terminal. | | `failed` | The payout did not complete (for example, a review timeout or a rejected compliance check). Terminal. | | `refunded` | The stablecoins were returned to the funding source instead of being converted to fiat. Terminal. | SWIFT payouts always start `on_hold` and stay there until compliance documents are submitted and approved. See [Payout quotes](/docs/payout-quotes#swift-compliance-documents) for the document submission flow. A payout that reaches `failed` or sits past review does not automatically refund the customer. If you see a payout stuck in review, contact support@blindpay.com rather than assuming it will resolve on its own. A payout that reaches `failed` or sits in review does not automatically refund the sender. If a payout looks stuck, contact support@blindpay.com rather than assuming it resolves on its own. ## Testing Development instances complete payouts automatically. Force a `failed` or `refunded` outcome by setting the payout quote's `request_amount` to one of these sentinel values before executing the payout: | `request_amount` | Result | | --- | --- | | `66600` ($666.00) | `failed` | | `77700` ($777.00) | `refunded` | | any other amount | `completed` | Development instances complete payouts automatically. Force a `failed` or `refunded` outcome by setting the payout quote's `request_amount` to one of these sentinel values before authorizing and executing the payout: | `request_amount` | Result | | --- | --- | | `66600` ($666.00) | `failed` | | `77700` ($777.00) | `refunded` (EVM payouts also fire a real on-chain refund transaction) | | any other amount | `completed` | ## Webhooks Configure a webhook endpoint once per instance. See [Webhooks](/docs/learn/webhooks) for setup and signature verification. | Event | Fires when | | --- | --- | | `payout.new` | A payout is created. | | `payout.update` | A payout changes status (for example, `processing` to `on_hold`). | | `payout.complete` | A payout reaches `completed`, `failed`, or `refunded`. | | `payout.partnerFee` | A payout completes and a partner fee is owed. See [Partner fees](/docs/learn/partner-fees). | A payout can also execute a registered bill instead of a bank account: its `payable_id` is set, the bank fields are null, and the [payable](/docs/payables) emits its own `payable.*` events alongside these. Correlate the two through `payable_id`/`payout_id` and never count both completes as two payments. ## Related * [Payout quotes](/docs/payout-quotes): lock the exchange rate and fee split before executing * [Payables](/docs/payables): register a bill (boleto, arrecadação, PIX code) and pay it through this same flow * [Bank accounts](/docs/bank-accounts): add and manage recipient bank accounts * [Payout with EVM](/docs/payout-evm), [Stellar](/docs/payout-stellar), and [Solana](/docs/payout-solana): on-chain authorization mechanics for external wallets * [Mint USDB](/docs/mint-usdb): fund test wallets on development instances * [Webhooks](/docs/learn/webhooks): full event catalogue and signature verification - [Payout with managed wallet](/docs/payout-managed-wallet): the REST-only funding path - [Payout with EVM](/docs/payout-evm): ERC-20 approve from an external wallet - [Payout with Stellar](/docs/payout-stellar): authorize, sign the XDR, create - [Payout with Solana](/docs/payout-solana): delegate the tokens, then create - [Payout quotes](/docs/payout-quotes): lock the exchange rate, fees, and the on-chain approval payload - [Mint USDB](/docs/mint-usdb): fund test wallets on development instances - [Supported chains](/docs/kb/supported-chains): the full chain and token matrix --- --- url: /docs/bank-accounts.md description: >- Add recipient bank accounts BlindPay pays out to, across SWIFT, ACH, wire, RTP, Pix, SPEI, ACH COP, Transfers, and SEPA rails. --- A bank account represents the recipient details BlindPay pays out to when you execute a payout. A customer can hold multiple bank accounts, and you can add bank accounts that belong to someone other than the customer. A bank account represents the recipient details BlindPay pays out to when a payout converts a customer's stablecoin balance to fiat. A customer can hold multiple bank accounts, and you can add bank accounts that belong to someone other than the customer. You can add **third-party bank accounts**: a customer named "John" can have a payout sent to a bank account belonging to "Jack". Set `recipient_relationship` to anything other than `first_party` to skip the name-match check. ## How it works All bank account data must be valid, even on development instances. Validation (regex, length, country rules) runs the same way regardless of instance type; only the downstream provider submission is skipped in development. ### Supported payout rails | `type` | Country | Estimated time of arrival | | --- | --- | --- | | `international_swift` | Global | ~5 business days | | `ach` | United States | ~2 business days | | `wire` | United States | ~1 business day | | `rtp` | United States | instant | | `pix` | Brazil | instant | | `spei_bitso` | Mexico | instant | | `ach_cop_bitso` | Colombia | ~1 business day | | `transfers_bitso` | Argentina | instant | | `sepa` | Europe (SEPA zone) | ~1 business day | High transaction volumes may affect estimated payout delivery times. ### Required fields per type | Type | Required fields | Notes | | --- | --- | --- | | `international_swift` | `name`, `account_class`, `recipient_relationship`, `swift_code_bic`, `swift_account_holder_name`, `swift_account_number_iban`, full beneficiary address, full bank address | See International SWIFT rules below | | `ach` | `name`, `recipient_relationship`, `beneficiary_name`, `routing_number`, `account_number`, `account_type`, `account_class`, `address_line_1`, `city`, `state_province_region`, `country`, `postal_code` | Blocked for `light` KYC customers | | `wire` | Same as `ach` | Blocked for `light` KYC customers | | `rtp` | Same as `wire` | `routing_number` must be RTP-eligible. Blocked for `light` KYC customers | | `pix` | `name`, `pix_key` | `pix_key` can be a CPF, CNPJ, phone, email, or random key | | `spei_bitso` | `name`, `spei_protocol`, `spei_clabe`, `beneficiary_name` | `spei_institution_code` required for `debitcard`/`phonenum` protocols | | `ach_cop_bitso` | `name`, `ach_cop_beneficiary_first_name`, `ach_cop_beneficiary_last_name`, `ach_cop_document_id`, `ach_cop_document_type`, `ach_cop_email`, `ach_cop_bank_code`, `ach_cop_bank_account`, `account_type` | | | `transfers_bitso` | `name`, `transfers_type`, `transfers_account`, `beneficiary_name` | `transfers_type` is `CVU`, `CBU`, or `ALIAS` | | `sepa` | `name`, `account_class`, `sepa_iban`, `sepa_beneficiary_bic`, `sepa_beneficiary_legal_name`, `sepa_beneficiary_address_line_1`, `sepa_beneficiary_city`, `sepa_beneficiary_postal_code`, `sepa_beneficiary_country` | The IBAN's country code must match `sepa_beneficiary_country`. Some destinations are individual-only; see [Payment methods](/docs/kb/payment-methods#sepa-destinations) | `account_type` is `checking` or `saving`. `account_class` is `individual` or `business`. `recipient_relationship` accepts `first_party`, `employee`, `independent_contractor`, `vendor_or_supplier`, `subsidiary_or_affiliate`, `merchant_or_partner`, `customer`, `landlord`, `family`, or `other`. ## Prerequisites You also need a [customer](/docs/overview) who has completed KYC. ## Add a bank account ```bash [🌎 International SWIFT] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "international_swift", "name": "Display Name", "account_class": "business", "swift_code_bic": "EXAMPLECHXXX", "swift_account_holder_name": "Example Beneficiary GmbH", "swift_account_number_iban": "CH4008735681787160333", "swift_beneficiary_address_line_1": "75 Example Strasse", "swift_beneficiary_country": "CN", "swift_beneficiary_city": "ZUG", "swift_beneficiary_state_province_region": "ZG", "swift_beneficiary_postal_code": "8008", "swift_bank_name": "Example Bank, N.A.", "swift_bank_address_line_1": "18-20 Example Lane", "swift_bank_address_line_2": "PO BOX 3941", "swift_bank_country": "CN", "swift_bank_city": "GENEVA", "swift_bank_state_province_region": "GE", "swift_bank_postal_code": "1221", "recipient_relationship": "vendor_or_supplier", "swift_payment_code": "cn_swift_cgoddr" }' ``` ```bash [🇺🇸 ACH] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "ach", "name": "Display Name", "beneficiary_name": "Jane Doe", "routing_number": "121000358", "account_number": "3211237578", "account_type": "checking", "account_class": "individual", "address_line_1": "Rua Jose Pena Medina, 150", "address_line_2": "Apt 902", "city": "Vila Velha", "state_province_region": "ES", "country": "BR", "postal_code": "29101320", "recipient_relationship": "first_party" }' ``` ```bash [🇺🇸 Wire] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "wire", "name": "Display Name", "beneficiary_name": "JANE DOE", "routing_number": "026073008", "account_number": "8211239565", "account_class": "individual", "address_line_1": "5 Penn Plaza", "city": "NY", "state_province_region": "NY", "country": "US", "postal_code": "10001", "recipient_relationship": "first_party" }' ``` ```bash [🇺🇸 RTP] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "rtp", "name": "Display Name", "beneficiary_name": "JANE DOE", "routing_number": "026073008", "account_number": "8211239565", "account_class": "individual", "address_line_1": "5 Penn Plaza", "city": "NY", "state_province_region": "NY", "country": "US", "postal_code": "10001", "recipient_relationship": "first_party" }' ``` ```bash [🇧🇷 Pix] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "pix", "name": "Display Name", "pix_key": "" }' ``` ```bash [🇲🇽 SPEI] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "spei_bitso", "name": "Display Name", "beneficiary_name": "", "spei_protocol": "", "spei_institution_code": "", "spei_clabe": "" }' ``` ```bash [🇨🇴 ACH COP] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "ach_cop_bitso", "name": "Display Name", "account_type": "checking", "ach_cop_beneficiary_first_name": "", "ach_cop_beneficiary_last_name": "", "ach_cop_document_id": "", "ach_cop_document_type": "", "ach_cop_email": "", "ach_cop_bank_code": "", "ach_cop_bank_account": "" }' ``` ```bash [🇦🇷 Transfers 3.0] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "transfers_bitso", "name": "Display Name", "beneficiary_name": "", "transfers_type": "", "transfers_account": "" }' ``` ```bash [🇪🇺 SEPA] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "type": "sepa", "name": "Display Name", "account_class": "individual", "sepa_iban": "DE89370400440532013000", "sepa_beneficiary_bic": "COBADEFFXXX", "sepa_beneficiary_legal_name": "", "sepa_beneficiary_address_line_1": "", "sepa_beneficiary_city": "", "sepa_beneficiary_postal_code": "", "sepa_beneficiary_country": "DE" }' ``` Save the bank account ID (`ba_...`) for use in payout quotes. ## Connect with Plaid This feature is gated by `subscription_features.plaid` on the instance. Contact BlindPay to enable it. Calling the endpoint below without it enabled returns a 400 `plaid_not_supported` error. Instead of entering ACH details manually, a customer can connect their bank account through Plaid. BlindPay reads the verified routing and account numbers directly from Plaid, so there's no manual entry and no micro-deposit wait. The resulting bank account is `type: "ach"`, carries the timestamp `plaid_connected_at`, and can fund an ACH payin by pull instead of a manual bank transfer; see [Payins](/docs/payins#pull-funding-from-a-plaid-connected-account). There is a single endpoint. It returns a `hosted_link_url`; send the customer there and BlindPay does the rest. ```bash [Create link] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts/plaid \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' ``` The call returns `{ "link_token": "...", "expiration": "...", "hosted_link_url": "..." }`. Open `hosted_link_url` in a browser tab or an external webview - Plaid Hosted Link cannot be embedded in an iframe. When the customer finishes, Plaid notifies BlindPay and the bank account is created automatically, one per account the customer selected. All the identity fields on it (beneficiary name, address, tax id) come from the customer record, never from the bank connection, so the account is always first party. Listen to the `bankAccount.new` webhook, or poll [List bank accounts](https://api.blindpay.com/reference#tag/bank-accounts/GET/v1/instances/{instance_id}/customers/{customer_id}/bank-accounts){target="\_blank"}, to know when it is available. The same connection is never turned into two bank accounts, even if Plaid redelivers the notification. BlindPay never returns or stores the Plaid access token in plaintext anywhere reachable from the API, logs, or webhook payloads. ## International SWIFT rules International SWIFT accounts have extra country-conditional required fields. **Business accounts** * `business_industry` (NAICS code) is required when the customer or account class is `"business"`. **Individual customers** * `tax_id` must be the local tax ID for the beneficiary's country (for example, CPF for Brazil, SSN for the US). It is validated and formatted per country where required. **Phone number** * `phone_number` is required when the beneficiary's country is one of: BR, CN, CO, HK, MY, MX, PH, UG, UY. **Tax ID** * `tax_id` is required when the beneficiary's country is one of: AR, BY, BR, CL, CN, CO, CR, EC, GT, HN, JP, KZ, KR, MX, PK, PE, PH, RU, TH, UY. For international SWIFT payouts, compliance documents are collected after the payout is created, not at quote creation time. The payout is placed `on_hold` until the required documents are submitted and approved. ## Response fields The response includes the bank account `id` (`ba_...`), `type`, and the fields you submitted. `account_number` is masked in responses, showing only the last 4 digits. ## Related * [Payouts](/docs/payouts): create a payout quote and execute a payout to this bank account * [Payout quotes](/docs/payout-quotes): lock the rate and fee before paying out * [Virtual accounts](/docs/virtual-accounts): a customer's dedicated deposit account * [Cut-off times](/docs/kb/cut-off-times): settlement windows by payment rail * [Webhooks](/docs/learn/webhooks): `bankAccount.new` fires on create - [Payouts](/docs/payouts): create a payout quote and execute a payout to this bank account - [Payout quotes](/docs/payout-quotes): lock the rate and fee before paying out - [Managed wallet](/docs/store): the stablecoin balance payouts pull from - [Cut-off times](/docs/kb/cut-off-times): settlement windows by payment rail - [Webhooks](/docs/learn/webhooks): `bankAccount.new` fires on create --- --- url: /docs/payout-quotes.md description: >- Lock the exchange rate and fee split before executing a payout, and see the exact fee breakdown and recipient amount in the response. --- A payout quote locks the exchange rate and fee for a payout before you execute it. A payout can only execute against a valid, unexpired quote. It tells you exactly how much leaves the funding source and how much the recipient's bank account receives. For a stablecoin-funded payout, the quote also returns the on-chain payload you need to authorize the token transfer: the ERC-20 contract address, ABI, and the exact decimal-adjusted amount for EVM chains. ## How it works A payout quote is created against a specific [bank account](/docs/bank-accounts) and expires 5 minutes after creation. The response includes the exact fee breakdown and the final amount the recipient receives, so you can show the customer a firm number before committing to the payout. For the fiat lens, treat the stablecoin leg as settlement plumbing: you pass a `network` and `token` to price the quote, but the funding source itself (a managed wallet balance in the common case) is covered on [Payouts](/docs/payouts). A payout quote is created against a specific [bank account](/docs/bank-accounts) and a `network` + `token` pair for the funding leg. It expires 5 minutes after creation. The response includes the fee breakdown, the final fiat amount the recipient receives, and (for EVM networks) the `contract` payload used to authorize the token pull. ### network and token `network` and `token` select the chain and stablecoin the funding source sends from. Availability depends on your instance type: | Instance | Networks | Tokens | | --- | --- | --- | | Development | `sepolia`, `base_sepolia`, `arbitrum_sepolia`, `polygon_amoy`, `stellar_testnet`, `solana_devnet` | `USDB` only | | Production | `ethereum`, `base`, `polygon`, `arbitrum`, `stellar`, `solana`, `tron` (beta) | `USDC` or `USDT` | Token and chain support also differ by token: | Token | Supported networks | | --- | --- | | `USDC` | `polygon`, `base`, `arbitrum`, `ethereum`, `stellar`, `solana` (and their dev equivalents) | | `USDT` | `polygon`, `ethereum`, `solana`, `tron` (and the dev equivalents, except `tron`) | | `USDB` | development testnets only: `polygon_amoy`, `base_sepolia`, `arbitrum_sepolia`, `sepolia`, `stellar_testnet`, `solana_devnet` | `tron` is a production-only network in beta: it has no development testnet, so it can't be exercised on a development instance. See [Supported chains](/docs/kb/supported-chains) for details. Passing a network and token that are not compatible returns a 400 with a message naming the unsupported pair. See [Supported chains](/docs/kb/supported-chains) for the full matrix. ### currency\_type `currency_type` tells the API which side of the payout `request_amount` is denominated in. On a payout quote, this is the opposite direction from a payin quote: | `currency_type` | `request_amount` is denominated in | | --- | --- | | `sender` | The stablecoin the funding source sends (the token leg) | | `receiver` | The fiat currency the bank account receives | Payin quotes use the same field name with the opposite meaning: on a payin quote, `sender` means the fiat the payer sends. Always check which quote type you're building against. ### cover\_fees Fees can be paid by either party: | Payer | Fee basis | API setting | Dashboard option | | --- | --- | --- | --- | | Customer | Deducted from the fiat amount the recipient receives | `cover_fees: false` | Keep "Cover all payout fees" off | | Sender | Added on top of the stablecoin amount sent, so the recipient receives the full amount | `cover_fees: true` | Enable "Cover all payout fees" | Customer-paid fees are the most common case. Sender-paid fees are typical for payroll, where the company wants the recipient to receive an exact amount. `request_amount` is an integer in minor units; it does not accept floats. To send `$100.00`, pass `10000`. `request_amount` is an integer in minor units; it does not accept floats. To send `100 USDC`, pass `10000`. ### SWIFT compliance documents SWIFT payouts need compliance documents, but they are collected **after** the payout is created, not at quote creation time. Once you create the payout it is placed `on_hold` until the required documents are submitted and approved. Documents are only required when the recipient relationship is not `first_party` (sending to a third party). Sending to your own SWIFT account never requires documents. ## Prerequisites You also need a [customer](/docs/overview) who has completed KYC and a [bank account](/docs/bank-accounts) with `status: "approved"`. ## Create a payout quote Check the required fields in the [BlindPay API Docs](https://api.blindpay.com/reference#tag/quotes/POST/v1/instances/{instance_id}/quotes){target="\_blank"}. The quote accepts exactly one destination: `bank_account_id` (shown below) or `payable_id` to pay a registered bill. See [Payables](/docs/payables#how-to-pay-one) for that variant, where the amount comes from the bill itself. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "bank_account_id": "ba_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 10000, "network": "sepolia", "token": "USDB" }' ``` ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/quotes', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ bank_account_id: 'ba_000000000000', currency_type: 'sender', cover_fees: false, request_amount: 10000, network: 'sepolia', token: 'USDB', }), } ) const quote = await response.json() ``` ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "bank_account_id": "ba_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 10000, "network": "sepolia", "token": "USDC" }' ``` ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/quotes', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ bank_account_id: 'ba_000000000000', currency_type: 'sender', cover_fees: false, request_amount: 10000, network: 'sepolia', token: 'USDC', }), } ) const quote = await response.json() ``` ### Response For EVM networks, the response includes a `contract` object: the payload you pass to your Ethereum library to call `approve` on the token contract, authorizing BlindPay to pull the quoted amount. ```json { "id": "qu_000000000000", "expires_at": 1712958191000, "commercial_quotation": 1, "blindpay_quotation": 0.998, "sender_amount": 10000, "receiver_amount": 9980, "partner_fee_amount": 0, "flat_fee": 20, "billing_fee_amount": null, "contract": { "address": "0x...", "abi": [], "functionName": "approve", "blindpayContractAddress": "0x...", "amount": "100000000", "network": { "name": "sepolia", "chainId": 11155111 } } } ``` | Field | Type | Notes | | --- | --- | --- | | `id` | string | The quote ID (`qu_...`). References the same prefix as payin and transfer quotes. | | `expires_at` | number | Epoch **milliseconds**. | | `commercial_quotation` | number | The raw market exchange rate. | | `blindpay_quotation` | number | The rate net of BlindPay's fee. | | `sender_amount` | number | The stablecoin amount the funding source sends, in minor units. | | `receiver_amount` | number | The fiat amount the bank account receives, in minor units. | | `partner_fee_amount` | number | Nonzero only if `partner_fee_id` was passed. See [partner fees](/docs/learn/partner-fees). | | `flat_fee` | number | The flat-fee component. | | `billing_fee_amount` | number, nullable | Only nonzero on instances with billing charges enabled. | | `contract` | object, nullable | The on-chain ERC-20 `approve` payload for the chosen token and network. Only relevant when funding from a self-custodied wallet; see [Payout with EVM](/docs/payout-evm). | Save the quote ID (`qu_...`). It expires in 5 minutes; if it expires before you execute the payout, create a new one. | `contract` | object, nullable | Present for EVM networks. The on-chain `approve` payload; see below. | `contract` fields: | Field | Type | Notes | | --- | --- | --- | | `contract.address` | string | The ERC-20 token contract address for the chosen token and network. | | `contract.abi` | array | The ERC-20 ABI, ready to pass to your Ethereum library. | | `contract.functionName` | string | Always `approve`. | | `contract.blindpayContractAddress` | string | The address to approve as spender: BlindPay's receiving address on that network. | | `contract.amount` | string | The decimal-adjusted amount to approve, as a string. | | `contract.network` | object | `{ name, chainId }` for the quoted network. | Stellar and Solana don't use the allowance pattern, so `contract` is not meaningful there: Stellar payouts sign an XDR transaction directly, and Solana payouts sign a token delegation. See [Payout with Stellar](/docs/payout-stellar) and [Payout with Solana](/docs/payout-solana) for the full authorization flows. Save the quote ID (`qu_...`). It expires in 5 minutes; if it expires before you execute the payout, create a new one. ## Related * [Payouts](/docs/payouts): execute the payout against this quote * [Bank accounts](/docs/bank-accounts): add and manage recipient bank accounts * [Partner fees](/docs/learn/partner-fees): pass a `partner_fee_id` to earn a cut of the payout * [Cut-off times](/docs/kb/cut-off-times): settlement windows by payment rail - [Payouts](/docs/payouts): the payout reference, status lifecycle, and links to every funding-path tutorial - [Bank accounts](/docs/bank-accounts): add and manage recipient bank accounts - [Supported chains](/docs/kb/supported-chains): the full chain and token matrix - [Partner fees](/docs/learn/partner-fees): pass a `partner_fee_id` to earn a cut of the payout --- --- url: /docs/payins.md description: >- Create a payin, deliver funds to the destination, and track it through settlement. --- Receive covers the fiat-in side of BlindPay: a customer sends a bank transfer (ACH, wire, Pix, SPEI, Transfers, or PSE), and BlindPay converts it to stablecoins and credits the customer's wallet or managed balance. This section is for teams collecting money from customers, such as onramps, remittance apps, or marketplaces paying in fiat. ## How it works Every payin starts with a quote that locks the payment method, amount, fee split, and destination for 5 minutes. Create the payin within that window. ``` payin quote -> payin (create within 5 min) -> BlindPay detects the deposit -> settled ``` 1. Create a payin quote. It returns the payment instructions to hand to the sender. 2. Create the payin, referencing the quote. 3. BlindPay watches for the deposit, converts it, and sends stablecoins to the customer. On development instances every payin completes automatically about 30 seconds after initiation. ### Payment methods | `payment_method` | Currency | What the sender sees | | --- | --- | --- | | `ach` | USD | `memo_code` + `blindpay_bank_details` | | `wire` | USD | `memo_code` + `blindpay_bank_details` | | `pix` | BRL | `pix_code` (copyable text or QR code) | | `spei` | MXN | CLABE number | | `transfers` | ARS | CBU number | | `pse` | COP | payment link | For `ach`/`wire`, the memo code is shown only when the customer has no enabled virtual account; with one, BlindPay displays dedicated account details instead and ignores the memo code. See [virtual accounts](/docs/virtual-accounts). ## Cover fees Fees can be paid by either party. Set `cover_fees` on the payin quote: | `cover_fees` | Who pays | Fee is deducted from | | --- | --- | --- | | `false` | The customer | The stablecoin amount the customer receives (most common) | | `true` | The sender | The fiat amount the sender sends, added on top | A payin is the object that actually moves money: it consumes a payin quote and tells BlindPay to start waiting for the customer's deposit. Everything about the payment (amount, method, fees, destination) was already locked in when you created the [payin quote](/docs/payin-quotes); the payin itself takes a single field. ## How it works ``` payin quote (pq_...) -> create the payin -> display instructions to the payer -> BlindPay detects the deposit -> settles ``` A payin quote expires 5 minutes after creation, so create the payin before then. Once created, a payin cannot be canceled: if the payer never sends the money, the payin simply stays `processing` until it is cleaned up on BlindPay's side. You also need a customer with a blockchain wallet or a managed wallet, and a payin quote (`pq_...`). A payin is BlindPay's on-ramp: fiat comes in from a sender, stablecoins go out to a wallet. This page is the payin reference: the flow, the destinations, statuses, testing, and webhooks. The step-by-step flows live in the per-destination tutorials. ## How it works 1. Create a [payin quote](/docs/payin-quotes) for the amount, payment method, and destination. The quote locks the exchange rate, the fees, and generates the payment instructions to hand to the sender. 2. Create the payin using the quote ID. This starts BlindPay watching for the deposit to arrive. 3. The sender completes the transfer using the payment instructions (a bank wire, a Pix code, a CLABE, a CBU, or a payment link, depending on the method). 4. Once the fiat lands, BlindPay converts it and delivers the equivalent stablecoins to the destination wallet, then fires a `payin.complete` webhook. A payin quote expires 5 minutes after creation, so create the payin before then. Once created, a payin cannot be canceled: if the sender never completes the deposit, the payin stays `processing` until it is cleaned up on BlindPay's side. The destination is set on the payin quote and is one of: | Field | Wallet type | Custody | Tutorial | | --- | --- | --- | --- | | `wallet_id` (`bl_...`) | Managed wallet | BlindPay-custodied | [Payin with managed wallet](/docs/payin-managed-wallet) | | `blockchain_wallet_id` (`bw_...`) | External blockchain wallet | Customer-controlled | [Payin with blockchain wallet](/docs/payin-blockchain-wallet) | A payin quote never targets a bank account (`ba_...`); that identifier belongs to the payout side. You also need a customer with a [managed wallet](/docs/wallets) (`bl_...`) or a [blockchain wallet](/docs/blockchain-wallets) (`bw_...`), and a [payin quote](/docs/payin-quotes) (`pq_...`). ## Create a payin Replace `pq_000000000000` with the payin quote you generated previously. ```bash [cURL] curl https://api.blindpay.com/v1/instances/in_000000000000/payins/evm \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "payin_quote_id": "pq_000000000000" }' ``` The endpoint path is `/payins/evm` for every payment method, including Pix, SPEI, Transfers, and PSE. The name is historical; it does not restrict which rail or network the payin uses. The endpoint path is `/payins/evm` regardless of the destination network. The name is historical; it does not restrict the payin to EVM chains, and it works identically whether the payin quote's destination is a Stellar, Solana, or EVM wallet. ### Response The response carries the payment instructions for whichever `payment_method` was set on the quote. Only the field relevant to that method is populated; the rest are `null`. ```json { "id": "pi_000000000000", "status": "processing", "pix_code": null, "memo_code": "8K45GHBNT6BQ6462", "clabe": null, "partner_fee": 0, "customer_id": "re_000000000000", "receiver_amount": 9900, "payment_method": "ach", "sender_amount": 10000, "billing_fee_amount": null, "transaction_fee_amount": 100, "blindpay_bank_details": { "routing_number": "121145349", "account_number": "621327727210181", "account_type": "Business checking", "beneficiary": { "name": "BlindPay, Inc.", "address_line_1": "8 The Green, #19364", "address_line_2": "Dover, DE 19901" }, "receiving_bank": { "name": "Example Bank, N.A.", "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", "address_line_2": "San Francisco, CA 94129" } }, "tracking_transaction": { "step": "processing" }, "tracking_payment": { "step": "on_hold" }, "tracking_complete": { "step": "on_hold" }, "tracking_partner_fee": { "step": "on_hold" } } ``` `receiver_amount` is the stablecoin amount, in minor units, that the destination wallet will receive once the deposit clears. The fiat-side fields (`memo_code`, `blindpay_bank_details`, `pix_code`, `clabe`) mirror whichever `payment_method` was set on the quote; only the relevant one is populated. ## What to display per method | `payment_method` | What to show the payer | Field(s) in the response | | --- | --- | --- | | `ach` | `memo_code` plus BlindPay's bank details, so the deposit can be matched to this payin | `memo_code`, `blindpay_bank_details` | | `wire` | Same as `ach` | `memo_code`, `blindpay_bank_details` | | `pix` | The Pix code as copyable text or a QR code | `pix_code` | | `spei` | The CLABE number | `clabe` | | `transfers` | The account number (CVU, CBU, or Alias, see `type`) | `tracking_transaction.transfers_instruction.account`, `tracking_transaction.transfers_instruction.type` | | `pse` | The payment link | `tracking_transaction.pse_instruction.payment_link` | If the customer has an approved virtual account, BlindPay displays their own dedicated account details instead, and `memo_code` is ignored even though the field is still returned. This applies to `ach`, `wire`, and `rtp`. ## Stablecoin delivery Once the fiat deposit is confirmed, BlindPay converts it and sends the equivalent stablecoins on-chain to the destination wallet you set on the payin quote (`blockchain_wallet_id` or `wallet_id`). Delivery happens automatically; there is no separate call to trigger it. | Chain | Tokens | | --- | --- | | Ethereum, Polygon (EVM) | USDC, USDT | | Base, Arbitrum (EVM) | USDC | | Stellar | USDC | | Solana | USDC, USDT | BlindPay detects the destination's network from the wallet record itself, so the same `POST /payins/evm` call works for every chain above; you don't select a network explicitly on the payin. Stellar mainnet deliveries originate from BlindPay's treasury wallet: `GCOSSQDM2SWMHRP7CDBOLL2V45NHCRLUWUCEHPPBA2ABCOOLPOLZKIHE`. This is the address that sends stablecoins to your blockchain wallet once the fiat payment is confirmed, and it is useful for reconciling incoming transactions on an explorer. ## Pull funding from a Plaid-connected account For `ach` payins, instead of the payer sending a manual bank transfer, BlindPay can pull the funds directly from a bank account the customer connected through [Plaid](/docs/bank-accounts#connect-with-plaid). Set `funding_bank_account_id` (a `ba_...` id) on the payin quote to a Plaid-connected account belonging to the same customer; the quote rejects any other bank account with 400 `funding_account_not_plaid_connected`. ```bash [cURL] curl https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": true, "request_amount": 10000, "payment_method": "ach", "token": "USDB", "funding_bank_account_id": "ba_000000000000" }' ``` Creating the payin from that quote triggers the pull automatically; there is no `memo_code` or `blindpay_bank_details` for the payer to act on, and no manual transfer for BlindPay to wait for. If the pull cannot be initiated, the payin fails immediately with `PAYINS_FUNDING_PULL_FAILED` instead of sitting in `processing`. Omit `funding_bank_account_id` to keep the default manual bank transfer flow described above. ## Monitoring window Once created, a payin waits for the fiat to actually land before it converts anything. On development instances every payin auto-completes about 30 seconds after creation, regardless of method. On production, the wait depends on the rail: On development instances every payin auto-completes about 30 seconds after creation, regardless of payment method. On production, BlindPay waits for the underlying fiat to actually land before converting and sending anything: | `payment_method` | Currency | Typical arrival window | | --- | --- | --- | | `ach` | USD | up to 5 business days | | `wire` | USD | up to 5 business days | | `pix` | BRL | up to 5 minutes | | `spei` | MXN | up to 10 minutes | | `transfers` | ARS | up to 10 minutes | | `pse` | COP | up to 10 minutes | See [cut-off times](/docs/kb/cut-off-times) for the full settlement-window reference. ## Status lifecycle | `status` | Meaning | Terminal? | | --- | --- | --- | | `processing` | Waiting for the deposit to arrive, or converting/sending stablecoins once it has | no | | `on_hold` | Held for manual review (risk or compliance) before continuing | no | | `completed` | Stablecoins delivered to the destination wallet | yes | | `failed` | The payin did not go through | yes | | `refunded` | The deposit was returned to the sender | yes | Each `tracking_*` object on the payin (`tracking_transaction`, `tracking_payment`, `tracking_complete`, `tracking_partner_fee`) exposes a finer-grained `step` (`processing`, `on_hold`, `pending_review`, `completed`) for the corresponding stage, useful for building a detailed status view. If a payin lands on-chain but the broadcast hash was replaced (for example during a gas spike), BlindPay automatically resolves the actual landed transaction. The `transaction_hash` you eventually see in `tracking_complete` may differ from the one you initially observed being broadcast; treat `status` as the source of truth, not a specific hash. If the on-chain delivery transaction gets replaced (for example during a gas spike), BlindPay automatically resolves the transaction that actually landed and re-broadcasts if needed. The `transaction_hash` you eventually see in `tracking_complete` may differ from the one you initially observed being broadcast; treat `status` as the source of truth, not a specific hash. ## Testing On development instances every payin auto-completes about 30 seconds after creation. Force a specific outcome instead by setting the payin quote's `request_amount` to one of these sentinel values: On development instances every payin auto-completes about 30 seconds after creation, using the sandbox `USDB` token. Force a specific outcome instead by setting the payin quote's `request_amount` to one of these sentinel values: | `request_amount` (minor units) | Result | | --- | --- | | `66600` ($666.00) | `failed` | | `77700` ($777.00) | `refunded` | | any other amount | `completed` after the normal ~30 second delay | ## Webhooks | Event | Fires when | | --- | --- | | `payin.new` | The payin is created | | `payin.update` | The payin's status or tracking data changes | | `payin.complete` | The payin reaches `completed` | See [webhooks](/docs/learn/webhooks) for signature verification and payload details. | Event | Fires when | | --- | --- | | `payin.new` | The payin is created | | `payin.update` | The payin's status or tracking data changes | | `payin.complete` | The payin reaches `completed`, meaning stablecoins were delivered to the destination wallet | See [webhooks](/docs/learn/webhooks) for signature verification and full payload details. ## Related * [Payin quotes](/docs/payin-quotes): lock the amount, method, and fee split before creating a payin * [Virtual accounts](/docs/virtual-accounts): give a customer their own dedicated account instead of a memo code * [Payouts](/docs/payouts): pay out from stablecoins to a bank account * [Webhooks](/docs/learn/webhooks): event payloads and signature verification * [Cut-off times](/docs/kb/cut-off-times): settlement windows by payment method - [Payin with managed wallet](/docs/payin-managed-wallet): the REST-only delivery path - [Payin with blockchain wallet](/docs/payin-blockchain-wallet): deliver to a customer-controlled wallet - [Payin quotes](/docs/payin-quotes): lock the amount, payment method, and destination wallet before creating a payin - [Blockchain wallets](/docs/blockchain-wallets): add the external wallet that receives delivered stablecoins - [Supported chains](/docs/kb/supported-chains): full chain and token matrix --- --- url: /docs/payin-quotes.md description: 'Lock the amount, fee split, and destination for a payin before creating it.' --- A payin quote locks in the numbers for a bank deposit before you commit to it: how much fiat the sender pays, how much the customer receives, the fee split, and the destination that settles the funds. You create a payin quote first, then create the [payin](/docs/payins) itself by referencing the quote's id. The quote is what actually enforces amount limits, currency rules, and payer requirements, so most of the validation work happens here rather than at payin creation. ## How it works A payin quote is a single call: pass the amount, the payment method, the fee setting, and the destination. BlindPay returns the locked-in fiat and stablecoin amounts plus the payment instructions for that method. The quote expires in 5 minutes, so create the payin shortly after. A payin quote locks in the numbers for an on-ramp before you commit to it: how much fiat the sender pays, how much stablecoin the destination wallet receives, the fee split, and the wallet the funds settle to. You create a payin quote first, then create the [payin](/docs/payins) itself by referencing the quote's id. The quote is what enforces amount limits, currency rules, and payer requirements, so most of the validation happens here rather than at payin creation. ``` payin quote -> payin (create within 5 minutes) ``` ## Destination The destination is a stablecoin delivery target, not a bank account. Pass exactly one of: The destination is a stablecoin wallet, never a bank account. Pass exactly one of: | Field | Points to | Prefix | | --- | --- | --- | | `blockchain_wallet_id` | An external blockchain wallet the customer controls | `bw_` | | `wallet_id` | A BlindPay-managed wallet | `bl_` | You cannot pass both, and you cannot pass neither. The stablecoin mechanics behind this destination are covered in [payins](/docs/payins); as a bank-rails integration you can treat it as an implementation detail. You cannot pass both, and you cannot pass neither: BlindPay rejects the request if either rule is violated. BlindPay reads the network directly off the destination wallet, so you never pass a network on the quote itself. See [wallets](/docs/wallets) and [blockchain wallets](/docs/blockchain-wallets) for how to register each type. ## Token and delivery network `token` is the stablecoin the destination wallet receives. The network is implied by the wallet you pass as the destination, and only certain token and network combinations have a deployed contract: | Chain | Tokens | | --- | --- | | Ethereum, Base, Polygon, Arbitrum (EVM) | USDC, USDT (USDT only on Polygon and Ethereum) | | Stellar | USDC | | Solana | USDC, USDT | | Tron | USDT only | Development instances only support the `USDB` test stablecoin, delivered on testnets (`sepolia`, `base_sepolia`, `arbitrum_sepolia`, `polygon_amoy`, `stellar_testnet`, `solana_devnet`). Production instances only support `USDC` or `USDT`, delivered on mainnets. The examples below use `USDB` since they target a development instance. ## currency\_type `currency_type` tells BlindPay which side `request_amount` is denominated in. On a payin quote, this is the opposite convention from a payout quote, so read it carefully: | `currency_type` | `request_amount` is denominated in | | --- | --- | | `sender` | The fiat currency the sender sends (determined by `payment_method`) | | `receiver` | The stablecoin the customer receives | `currency_type` tells BlindPay which side `request_amount` is denominated in. On a payin quote, `sender` means fiat, which is the opposite convention from a payout quote (where `sender` means stablecoin), so read it carefully: | `currency_type` | `request_amount` is denominated in | | --- | --- | | `sender` | The fiat currency the sender sends (determined by `payment_method`) | | `receiver` | The stablecoin the destination wallet receives | ## cover\_fees Fees can be paid by either party: | `cover_fees` | Who pays | Fee is deducted from | | --- | --- | --- | | `false` | The customer | The stablecoin amount the customer receives (most common) | | `true` | The sender | Added on top of the fiat amount the sender sends | | `cover_fees` | Who pays | Fee is deducted from | | --- | --- | --- | | `false` | The customer | The stablecoin amount the destination wallet receives (most common) | | `true` | The sender | Added on top of the fiat amount the sender sends | ## request\_amount `request_amount` is an integer in minor units and does not accept floats. To send `$123.45`, pass `12345`. Minimum and maximum amounts vary by currency, and the quote enforces them for you: if `request_amount` is outside the allowed range for that currency, the quote request fails with a dynamic error naming the min and max. Most currencies allow amounts as low as roughly $10 equivalent, but some (for example COP) require a much higher minimum in raw minor units because of the currency's smaller nominal value. Don't hardcode a single minimum across currencies; read the error if you hit the floor. The examples below use the `USDB` test stablecoin, only available on development instances. In production use `USDC` or `USDT`. Minimum and maximum amounts vary by currency, and the quote enforces them for you: if `request_amount` falls outside the allowed range for that currency, the request fails with a dynamic error naming the min and max. Most currencies allow amounts as low as roughly $10 equivalent, but some (for example COP) require a much higher minimum in raw minor units because of the currency's smaller nominal value. Don't hardcode a single minimum across currencies; read the error if you hit the floor. ## Prerequisites You also need a customer with a blockchain wallet or a virtual account. You also need a customer with a [blockchain wallet](/docs/blockchain-wallets) or a [managed wallet](/docs/wallets). ## Create a payin quote ```bash [🇺🇸 ACH] curl https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": true, "request_amount": 10000, "payment_method": "ach", "token": "USDB" }' ``` ```bash [🇺🇸 Wire] curl https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": true, "request_amount": 10000, "payment_method": "wire", "token": "USDB" }' ``` ```bash [🇧🇷 Pix] curl https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 10000, "payment_method": "pix", "token": "USDB", "payer_rules": { "pix_allowed_tax_ids": [ "14747677786" ] } }' ``` ```bash [🇲🇽 SPEI] curl https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 100000, "payment_method": "spei", "token": "USDB" }' ``` ```bash [🇦🇷 Transfers] curl https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 2000000, "payment_method": "transfers", "token": "USDB", "payer_rules": { "transfers_allowed_tax_id": "30-27383762-7" } }' ``` ```bash [🇨🇴 PSE] curl https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 20000000, "payment_method": "pse", "token": "USDB", "payer_rules": { "pse_full_name": "", "pse_document_type": "NIT", "pse_document_number": "", "pse_email": "", "pse_phone": "", "pse_bank_code": "" } }' ``` To target a managed wallet instead of a blockchain wallet, replace `blockchain_wallet_id` with `wallet_id` (`bl_000000000000`) in any of the payloads above. ### payer\_rules Some payment methods require payer identity fields so BlindPay can match and screen the incoming deposit: | `payment_method` | `payer_rules` field | Notes | | --- | --- | --- | | `pix` | `pix_allowed_tax_ids` | Array of CPF/CNPJ tax ids allowed to send this Pix | | `transfers` | `transfers_allowed_tax_id` | CUIT/CUIL tax id, required for `transfers` | | `pse` | `pse_full_name`, `pse_document_type`, `pse_document_number`, `pse_email`, `pse_phone`, `pse_bank_code` | Full payer details, required for `pse` | ## funding\_bank\_account\_id (ach pull) For `payment_method: "ach"`, you can optionally pass `funding_bank_account_id` (a `ba_...` id) to have BlindPay pull the funds from a bank account the customer already connected through [Plaid](/docs/bank-accounts#connect-with-plaid), instead of the payer sending a manual bank transfer. The account must belong to the same customer and be Plaid-connected (`plaid_connected_at` set); otherwise the quote is rejected with 400 `funding_account_not_plaid_connected`. See [Payins](/docs/payins#pull-funding-from-a-plaid-connected-account) for the full pull flow. ```bash [🇺🇸 ACH pull] curl https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": true, "request_amount": 10000, "payment_method": "ach", "token": "USDB", "funding_bank_account_id": "ba_000000000000" }' ``` ## Response | Field | Type | Notes | | --- | --- | --- | | `id` | string (`qu_`) | Pass this as `payin_quote_id` when creating the payin | | `expires_at` | number | Epoch milliseconds. The quote is valid for 5 minutes | | `sender_amount` | number | Fiat amount, in minor units, the sender must pay | | `receiver_amount` | number | Stablecoin amount the destination wallet receives | | `commercial_quotation` | number | Raw market exchange rate | | `blindpay_quotation` | number | Exchange rate including BlindPay's fee | | `flat_fee` | number | Flat-fee component of the quote | | `partner_fee_amount` | number | Nonzero only when `partner_fee_id` is set | Show the payer whichever field is relevant to their payment method once you create the payin: `memo_code` and `blindpay_bank_details` for `ach`/`wire`, `pix_code` for `pix`, a CLABE for `spei`, a CBU for `transfers`, or a payment link for `pse`. Those fields live on the [payin](/docs/payins) response, not the quote. The stablecoins themselves aren't sent yet at this point: creating the quote only locks the numbers. The [payin](/docs/payins) you create from this quote is what triggers the fiat collection and the on-chain delivery to the destination wallet. ### What the payin shows the payer The payin quote's `id` doesn't carry payer-facing instructions; those appear once you create the [payin](/docs/payins) from the quote. Depending on `payment_method`, the payin response returns: | `payment_method` | Field | Notes | | --- | --- | --- | | `ach`, `wire` | `memo_code` and `blindpay_bank_details` | Include the memo code with the transfer so BlindPay can match it. Ignored when the customer has an approved virtual account, since the payer sends to their own dedicated account instead | | `pix` | `pix_code` | The Pix code (copia e cola) for the payer to complete the transfer | See [payins](/docs/payins) for the full response reference and field descriptions. ## Expiry A payin quote expires **5 minutes** after creation. Create the payin before then; an expired quote is rejected when you try to use it. Generate a new quote if the window has passed. ## Partner fees Pass `partner_fee_id` (prefix `pf_`) to attribute a payin to a partner fee configured in the dashboard. The fee is snapshotted at quote time and reflected in `partner_fee_amount` on the response. See [partner fees](/docs/learn/partner-fees) for how the fee is calculated and collected. ## Testing On development instances, the amount you request determines the outcome once the resulting payin is created: | Amount | Result | | --- | --- | | 666.00 | Failed | | 777.00 | Refunded | | Any other amount | Completes automatically, about 30 seconds after initiation | | Amount | Result | | --- | --- | | 666.00 | Failed | | 777.00 | Refunded | | Any other amount | Completes automatically, about 30 seconds after initiation, and delivers `USDB` to the destination wallet on the matching testnet | ## Related * [Payins](/docs/payins): create the payin from a quote and track it to completion * [Virtual accounts](/docs/virtual-accounts): an alternative destination that skips the memo-code flow * [Partner fees](/docs/learn/partner-fees): how `partner_fee_id` is calculated and paid out * [Cut-off times](/docs/kb/cut-off-times): settlement windows by payment method - [Payins](/docs/payins): create the payin from a quote and track the on-chain delivery to completion - [Blockchain wallets](/docs/blockchain-wallets): register the external wallet a payin quote can target - [Wallets](/docs/wallets): the BlindPay-managed wallet alternative to a blockchain wallet - [Supported chains](/docs/kb/supported-chains): full chain and token compatibility matrix - [Partner fees](/docs/learn/partner-fees): how `partner_fee_id` is calculated and paid out --- --- url: /docs/payables.md description: >- Register a bill, an invoice, a boleto, or a PIX code, then pay it with the standard payout flow. --- A payable is a bill you register: an invoice, a boleto, or a PIX code. Once it exists, you pay it exactly like any other payout: quote it, then execute the payout. That is what separates a payable from a bank account payout. A bank account payout has no amount until you quote it, because you decide who gets paid and how much. A payable's amount instead comes from what you registered (line items, taxes, discount), or, for a boleto, from what the rail resolves at quote time. A payable is its own transaction, prefixed `pb_`, and belongs to a [customer](/docs/overview). ## What you can register A payable needs exactly one destination: | Destination | Field(s) | Payable today? | | --- | --- | --- | | Boleto | `boleto_barcode`: 47-digit linha digitável or 44-digit barcode not starting with 8 | Yes | | Utility / tax bill (arrecadação) | `boleto_barcode`: 48-digit linha digitável or 44-digit barcode starting with 8 | Yes | | PIX code | `pix_qrcode`: EMV copia e cola payload | Yes | | Bank details | `routing_number` + `account_number` (plus `swift_code_bic`, `bank_name`) | Not yet | Utility and tax bills go in the same `boleto_barcode` field; the kind of bill is detected from the code itself and confirmed at registration. Unlike a boleto, an arrecadação code carries its amount in the barcode (it never accrues interest between quote and payment) and has no due date or registered payer. On top of a destination, a payable can carry a full invoice shape: `from`/`to` parties (`legal_name`, address fields, `tax_id`), `line_items` (`name`, `quantity`, `price`), a `note`, `discount`, and `taxes`. Invoices increasingly look like this: a line-itemized bill with a boleto or PIX code attached, not just a bare code. Code payables (boleto, arrecadação, PIX) must be in `BRL`. EVM networks only, for now. ## Register a payable ### Boleto ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payables \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "customer_id": "re_000000000000", "currency": "BRL", "boleto_barcode": "34191790010104351004791020150008191070026000", "due_date": "2026-08-25" }' ``` ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payables', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ customer_id: 're_000000000000', currency: 'BRL', boleto_barcode: '34191790010104351004791020150008191070026000', due_date: '2026-08-25', }), } ) const payable = await response.json() ``` ```json { "id": "pb_000000000000", "instance_id": "in_000000000000", "customer_id": "re_000000000000", "from": null, "to": { "legal_name": "ACME ENERGIA LTDA", "tax_id": "12.345.678/0001-90" }, "currency": "BRL", "line_items": [{ "name": "ACME ENERGIA LTDA", "quantity": 1, "price": 26000 }], "note": null, "discount": 0, "taxes": 0, "routing_number": null, "account_number": null, "swift_code_bic": null, "bank_name": null, "boleto_barcode": "34191790010104351004791020150008191070026000", "pix_qrcode": null, "due_date": "2026-08-25", "scheduled_at": null, "status": "draft", "amount": 26000, "created_at": "2026-08-18T12:00:00.000Z", "updated_at": "2026-08-18T12:00:00.000Z" } ``` The code is resolved at registration, so the beneficiary, due date and current `amount` come back real (as a single auto line item). See [Amount semantics](#amount-semantics) for what actually gets charged at payment time. ### PIX ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payables \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "customer_id": "re_000000000000", "currency": "BRL", "pix_qrcode": "00020126580014br.gov.bcb.pix0136..." }' ``` ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payables', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ customer_id: 're_000000000000', currency: 'BRL', pix_qrcode: '00020126580014br.gov.bcb.pix0136...', }), } ) const payable = await response.json() ``` ### Invoice, with line items An invoice can carry `from`/`to` parties and itemized charges. The destination here is inline bank details, which registers the invoice but cannot be paid yet (see the table above). ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payables \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "customer_id": "re_000000000000", "from": { "legal_name": "Acme Inc", "address_line_1": "123 Main St", "city": "Austin", "state_province_region": "TX", "postal_code": "78701", "country": "US", "tax_id": "12-3456789" }, "to": { "legal_name": "Empresa de Servicos LTDA", "country": "BR", "tax_id": "12.345.678/0001-90" }, "currency": "USD", "line_items": [ { "name": "Consulting services", "quantity": 2, "price": 150000 }, { "name": "Software license", "quantity": 1, "price": 50000 } ], "note": "August retainer", "discount": 10000, "taxes": 5000, "routing_number": "021000021", "account_number": "000123456789", "swift_code_bic": "CHASUS33", "bank_name": "Chase Bank", "due_date": "2026-09-01" }' ``` ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payables', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ customer_id: 're_000000000000', from: { legal_name: 'Acme Inc', address_line_1: '123 Main St', city: 'Austin', state_province_region: 'TX', postal_code: '78701', country: 'US', tax_id: '12-3456789', }, to: { legal_name: 'Empresa de Servicos LTDA', country: 'BR', tax_id: '12.345.678/0001-90', }, currency: 'USD', line_items: [ { name: 'Consulting services', quantity: 2, price: 150000 }, { name: 'Software license', quantity: 1, price: 50000 }, ], note: 'August retainer', discount: 10000, taxes: 5000, routing_number: '021000021', account_number: '000123456789', swift_code_bic: 'CHASUS33', bank_name: 'Chase Bank', due_date: '2026-09-01', }), } ) const payable = await response.json() ``` `line_items[].price` is the unit price in cents, so `150000` is $1,500.00. The response's `amount` is `345000`: `(2 × 150000) + (1 × 50000) + 5000 taxes - 10000 discount`. ## List payables ```bash [cURL] curl --url 'https://api.blindpay.com/v1/instances/in_000000000000/payables?status=draft&customer_id=re_000000000000' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` Filter by `status` or `customer_id`. Pass `limit`, `starting_after`, or `ending_before` for cursor pagination; omit them and you get the full array back instead of a paginated envelope. ## Retrieve a payable ```bash [cURL] curl --url https://api.blindpay.com/v1/instances/in_000000000000/payables/pb_000000000000 \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ## Delete a payable A `draft` payable (no payout executing it yet) can be deleted: ```bash [cURL] curl --request DELETE \ --url https://api.blindpay.com/v1/instances/in_000000000000/payables/pb_000000000000 \ --header 'Authorization: Bearer YOUR_API_KEY' ``` The payable moves to the terminal `canceled` status, a `payable.update` event fires, and the same code can be registered again afterwards. Once any payout exists for the payable the delete is rejected with `payable_not_cancelable`. There is no update endpoint and no separate quote/pay/reprice/receipt endpoint for payables: paying one is a normal payout. ## Amount semantics `amount` on a payable is derived: the sum of `line_items[].price × line_items[].quantity`, plus `taxes`, minus `discount`, all in cents. That is what the invoice says is owed. For a boleto, that derived `amount` is not necessarily what gets charged. The provider re-resolves the current amount from the barcode at quote time, which can include fines, interest, or a discount that only apply on certain days. A bare boleto payable (no line items) always shows `amount: 0` until you quote it; the quote's `receiver_amount` is the real number. Quote an overdue boleto twice on different days and you can get two different amounts, because interest keeps accruing. Always pay against a fresh quote rather than a cached figure. PIX and invoice-with-bank-details payables use the declared `amount` as-is; there is no rail-side recalculation for those. ## Status lifecycle ``` draft ─┬─→ canceled (DELETE) └─→ processing ─┬─→ completed └─→ back to draft (attempt failed or was refunded) ``` A payable has four statuses, and they describe the bill, not the payment attempt: | Status | Meaning | | --- | --- | | `draft` | Registered, no payout executing it. Quotable. A failed or refunded attempt returns the payable here. | | `processing` | A payout is executing it, including while that payout is held for review or being refunded. | | `completed` | Paid. Terminal, never regresses. | | `canceled` | A draft was deleted. Terminal; the code can be registered again. | The attempt's own lifecycle (compliance hold, release, failure reason, refund) lives on the payout, reachable through the payable's `payout_id`. To retry after a failed attempt, just quote the same payable again. ## Dedupe Registering the same `boleto_barcode` or `pix_qrcode` twice for the same instance is rejected with `duplicate_payable`: each code has exactly one payable. If a payment attempt fails or is refunded, do not register the code again. Quote the same payable again and pay it. ## How to pay one Paying a payable is the standard payout flow, not a dedicated endpoint: create a quote with `payable_id`, then execute the payout. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "payable_id": "pb_000000000000", "network": "sepolia", "token": "USDB" }' ``` Exactly one of `bank_account_id` or `payable_id` is accepted. With `payable_id`, do not send `request_amount` or `currency_type`: the amount comes from the payable (re-resolved from the rail for a boleto). Save the quote's `id`, then execute the payout the normal way: ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "YOUR_WALLET_ADDRESS" }' ``` The quote response carries the same `contract` object a bank account quote does, with the ERC-20 address, ABI, and decimal-adjusted amount to approve, when the funding wallet is external. See [Payable with EVM](/docs/payable-evm) and [Payable with managed wallet](/docs/payable-managed-wallet) for the full walk-throughs. Quotes expire 5 minutes after creation, same as any other quote. To reprice a payable, whether the quote expired or the amount changed, just create a new quote for the same `payable_id`. ## The payout behind a payable Executing a payable creates a regular payout. It shows up in the payouts list and retrieve like any other payout, with `payable_id` set instead of a bank account; the payable's `payout_id` points back at it. Both objects describe the same single payment. ## Webhooks Payables emit their own events, and the payload is the same shape the payable endpoints return: * `payable.new`: the payable was registered (status `draft`). * `payable.update`: the bill changed; a payout claimed it (`processing`), the attempt failed or was refunded (back to `draft`, payable again), or a draft was deleted (`canceled`). A draft-return payload carries the attempt's `payout_id` and `last_attempt_status` (`failed` or `refunded`), so it is never mistaken for a never-attempted payable. * `payable.complete`: the payable was paid (status `completed`). The payout emits its normal `payout.new`/`payout.update`/`payout.complete` events, carrying `payable_id`. The mid-flight detail (compliance hold, release, failure reason, refund) arrives only there; the payable stays `processing` through all of it. One paid bill produces both streams: correlate them through `payable_id`/`payout_id` and never count a `payout.complete` and its `payable.complete` as two payments. ## Related * [Payouts](/docs/payouts): the flow that actually pays a payable - [Payable with EVM](/docs/payable-evm): pay from an external wallet - [Payable with managed wallet](/docs/payable-managed-wallet): pay from a BlindPay-custodied wallet --- --- url: /docs/learn/api-keys.md description: >- Authenticate all BlindPay API requests with an instance-scoped API key created in the dashboard. --- An API key authenticates your requests to the BlindPay API. Each key is scoped to a single instance: a key created for one instance will not work against another, and a key created on a development instance will not work against a production instance. ## Create an API key Go to the [BlindPay dashboard](https://app.blindpay.com), select an instance, and open the **API Keys** tab. Keys are shown once at creation. Copy and store them securely: you cannot retrieve the key value again after leaving the creation screen. The dashboard only ever displays a short prefix of the key afterward, for identification purposes. Creating a key requires a dashboard (user) session; it cannot be done from an existing API key. This prevents a compromised key from minting new keys for itself. ## Authenticate requests Pass the key as a bearer token in every request: ```bash [cURL] curl https://api.blindpay.com/v1/instances/in_000000000000/customers \ --header 'Authorization: Bearer YOUR_API_KEY' ``` Done. Authenticate all requests by passing the header `Authorization: Bearer YOUR_API_KEY`. ## Security best practices * Never expose API keys in client-side code, mobile apps, or version control. * Use environment variables to inject keys at runtime instead of hardcoding them. * Rotate keys immediately if you suspect a key has been compromised: delete the old key and create a new one in the dashboard. There is no separate rotate endpoint; delete and recreate is the only path, and deleting a key revokes it immediately. * Use separate keys per environment: one for development, one for production. Keys created on a development instance will not work against a production instance, and vice versa. * Keys accept an IP whitelist at creation time via the API if your integration calls the API from a fixed set of servers. ## Key types The dashboard currently creates one type of key, with full read and write access to the instance. A restricted or read-only key type with scoped permissions is not yet available, so every key for an instance carries the same level of access. Keep this in mind when deciding who on your team gets a key, since there is no way to limit a given key to, for example, read-only access. ## Related * [Instances](/docs/learn/instances): each API key is scoped to one instance * [Sandbox vs. production](/docs/learn/sandbox-vs-production): use separate keys per environment * [Webhooks](/docs/learn/webhooks): a complementary way to receive updates without polling the API * [Quickstart](/docs/quickstart-payin): see an API key used in your first request --- --- url: /docs/learn/webhooks.md description: >- Receive real-time events for customers, payments, virtual accounts, wallets, and transfers instead of polling the API. --- A webhook is an HTTP callback that BlindPay sends to your server when something changes: a payin completes, a customer is created, a virtual account is approved. Subscribe once and receive every event as it happens, instead of polling the API for status. Webhooks are configured per [instance](/docs/learn/instances). You can register up to 25 endpoints on the same instance if you want to split traffic across services. ## Create a webhook Register your endpoint URL on the instance. The URL must be `https`; local or private addresses are rejected. Pass an empty `events` array to receive every event, or list specific events to subscribe to only those. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/webhook-endpoints \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "url": "https://example.com/webhook", "events": [] }' ``` The response returns the endpoint ID (`we_...`). To verify the signatures on incoming calls, fetch the endpoint's signing secret: ```bash [cURL] curl --request GET \ --url https://api.blindpay.com/v1/instances/in_000000000000/webhook-endpoints/we_000000000000/secret \ --header 'Authorization: Bearer YOUR_API_KEY' ``` This returns `{ "key": "whsec_..." }`; see [Verification](/docs/learn/webhooks-verification) for how to use it. You can also manage endpoints from the [BlindPay dashboard](https://app.blindpay.com), under the instance's **Webhooks** tab. For quick testing before you have a real handler, point the endpoint at [webhook.cool](https://webhook.cool/): it gives you a temporary URL that logs every incoming request so you can inspect a payload first. ### Key events for fiat Working with the fiat flavor, you will listen most often to: * `virtualAccount.complete`: the account is approved and ready to receive deposits * `payin.complete`: a deposit arrived and settled * `payout.complete`: a bank transfer was sent, failed, or was refunded See [Events](/docs/learn/webhooks-events) for the full catalog. ### Key events for stablecoin Working with the stablecoin flavor, you will listen most often to: * `wallet.inbound`: stablecoins were deposited into a managed wallet * `payin.complete`: fiat was received and stablecoins were delivered * `payout.complete`: stablecoins were pulled and fiat was sent See [Events](/docs/learn/webhooks-events) for the full catalog. Every webhook call is signed. Verify the `svix-id`, `svix-timestamp`, and `svix-signature` headers before trusting a payload; see Verification for the full process. ## In this section * [Events](/docs/learn/webhooks-events): the full event catalog, payload shapes, and when each one fires * [Verification](/docs/learn/webhooks-verification): how to verify the signature headers on every call ## Related * [Instances](/docs/learn/instances): webhooks are configured per instance * [API keys](/docs/learn/api-keys): authenticate the rest of the API alongside your webhook endpoint * [Partner fees](/docs/learn/partner-fees): how partner fee tracking fields appear on payin/payout webhooks * [Payin quickstart](/docs/quickstart-payin): see webhooks in a full payment flow * [Payout quickstart](/docs/quickstart-payout): see webhooks in a payout flow --- --- url: /docs/learn/webhooks-events.md description: >- The full BlindPay webhook event catalog, grouped by domain, with an example payload. --- Every webhook endpoint you register receives events from the catalog below, unless you narrow the `events` array to a subset when creating the endpoint. Events are grouped by the resource domain that triggers them. `customer.new`, `customer.update`, and `customer.delete` are the canonical customer lifecycle events. The legacy `receiver.*` events were retired with the July 2026 receivers-to-customers sunset and no longer fire. ## Customer | Event | Fires when | | --- | --- | | `customer.new` | A customer is created | | `customer.update` | A customer is updated, including a KYC status change from BlindPay's compliance review | | `customer.delete` | A customer is deleted | ## Bank account | Event | Fires when | | --- | --- | | `bankAccount.new` | A bank account is added to a customer | ## Blockchain wallet | Event | Fires when | | --- | --- | | `blockchainWallet.new` | A blockchain wallet is added to a customer | ## Terms of service | Event | Fires when | | --- | --- | | `tos.accept` | A customer accepts the terms of service | ## Payin | Event | Fires when | | --- | --- | | `payin.new` | A payin is created (any payment method, or a deposit into a virtual account) | | `payin.update` | The payin advances through an intermediate step, such as an arrival check or manual review | | `payin.complete` | The payin finishes: sent to the destination wallet, refunded, or failed | ## Payout | Event | Fires when | | --- | --- | | `payout.new` | A payout is created and the source funds have been captured | | `payout.update` | The payout advances through an intermediate step, such as document checks, manual review, or the bank send | | `payout.complete` | The payout finishes: confirmed at the destination, refunded, or failed | ## Virtual account | Event | Fires when | | --- | --- | | `virtualAccount.new` | A virtual account is created or requested for a customer | | `virtualAccount.complete` | The virtual account is approved and an account number is issued | ## Transfer | Event | Fires when | | --- | --- | | `transfer.new` | A stablecoin [transfer](/docs/send) is created | | `transfer.complete` | The transfer confirms on-chain | ## Wallet | Event | Fires when | | --- | --- | | `wallet.new` | A managed [wallet](/docs/wallets) is created for a customer | | `wallet.inbound` | A stablecoin deposit is detected in a managed wallet | ## Limit increase | Event | Fires when | | --- | --- | | `limitIncrease.new` | A customer requests a transaction limit increase | | `limitIncrease.update` | BlindPay's compliance review approves or rejects the request | ## Key events for fiat If you are only working with fiat rails, the events you will see most often are: * `virtualAccount.complete`: an account is approved and ready to receive deposits * `payin.complete`: a deposit arrived and settled * `payout.new` and `payout.complete`: a bank transfer started, then finished, failed, or was refunded * `bankAccount.new`: a payout destination was added to a customer See [Payin quotes](/docs/payin-quotes) and [Payout quotes](/docs/payout-quotes) for the requests that lead to these events. ## Key events for stablecoin If you are only working with stablecoin rails, the events you will see most often are: * `wallet.inbound`: stablecoins landed in a managed wallet * `blockchainWallet.new`: a customer added an external wallet as a payout destination * `payin.complete` and `payout.complete`: fiat-to-stablecoin and stablecoin-to-fiat legs finished * `transfer.new` and `transfer.complete`: an on-chain stablecoin transfer started and confirmed See [Blockchain wallets](/docs/blockchain-wallets) and [Wallets](/docs/wallets) for how these destinations are created. ## Example payload Every payload includes a `webhook_event` field set to the event name, followed by the resource's own fields. This example is a `payin.complete` event: ```json { "webhook_event": "payin.complete", "id": "pi_000000000000", "status": "completed", "customer_id": "re_000000000000", "receiver_amount": 10000, "sender_amount": 10000, "payment_method": "ach", "partner_fee": 0, "billing_fee_amount": 0, "transaction_fee_amount": 250, "blindpay_bank_details": { "bank_name": "Example Bank, N.A.", "account_number": "000000000000", "routing_number": "000000000" }, "memo_code": "BP-AB12CD", "pix_code": null, "clabe": null, "tracking_complete": { "step": "completed", "completed_at": "2026-06-01T12:00:00.000Z" }, "tracking_payment": { "step": "completed", "completed_at": "2026-06-01T11:58:00.000Z" }, "tracking_transaction": { "step": "completed", "transaction_hash": "0x0000000000000000000000000000000000000000000000000000000000000", "completed_at": "2026-06-01T11:59:00.000Z" }, "tracking_partner_fee": { "step": "on_hold", "transaction_hash": null, "completed_at": null } } ``` ## Related * [Webhooks](/docs/learn/webhooks): create an endpoint and see webhooks in context * [Webhooks verification](/docs/learn/webhooks-verification): verify the signature headers on every call * [Payins](/docs/payins): the payin lifecycle behind `payin.*` events * [Payouts](/docs/payouts): the payout lifecycle behind `payout.*` events * [Partner fees](/docs/learn/partner-fees): how `tracking_partner_fee` is tracked on quotes and transactions --- --- url: /docs/learn/webhooks-verification.md description: >- Verify the signature on every BlindPay webhook call using the svix-id, svix-timestamp, and svix-signature headers. --- Every webhook call BlindPay sends is signed so you can confirm it actually came from BlindPay and was not tampered with in transit. Verify the signature before you act on any payload. ## Headers | Header | Description | | --- | --- | | `svix-id` | Unique message identifier. Stays the same across redelivery attempts of the same event, so you can use it to deduplicate. | | `svix-timestamp` | Signing timestamp, in seconds since the Unix epoch. | | `svix-signature` | Space-delimited list of signatures, each prefixed with a version tag (for example `v1,`). | ## Verification process ### 1. Construct the signed content Concatenate the message ID, the timestamp, and the raw request body with `.` between each part: ```javascript const signedContent = `${svixId}.${svixTimestamp}.${body}` ``` Use the exact raw body bytes received on the wire. Re-serializing the parsed JSON can reorder keys or change whitespace, which invalidates the signature. ### 2. Compute the expected signature Your signing secret has the form `whsec_`. Take the part after the underscore, base64-decode it into raw bytes, and use those bytes as the HMAC-SHA256 key: ```javascript const crypto = require('node:crypto') const secretBytes = Buffer.from(secret.split('_')[1], 'base64') const expectedSignature = crypto .createHmac('sha256', secretBytes) .update(signedContent) .digest('base64') ``` ### 3. Compare against the header, in constant time `svix-signature` can contain more than one signature (for example during secret rotation), space-delimited, each with a `v1,` version prefix: ``` v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bYCGmRKsaKWcsNccKXlIktD0/rAvfW3dJ/X/qxh= ``` Strip the `v1,` prefix from each candidate and compare it against your computed signature with a constant-time comparison, not `===`. Accept the request if any candidate matches: ```javascript const signatures = svixSignature .split(' ') .map(sig => sig.split(',')[1]) const isValid = signatures.some(sig => crypto.timingSafeEqual( Buffer.from(sig, 'base64'), Buffer.from(expectedSignature, 'base64'), ), ) ``` ### 4. Check the timestamp tolerance Compare `svix-timestamp` against your own system clock and reject the request if it falls outside a tolerance window (5 minutes is a reasonable default). This blocks replay attacks that resend a previously valid, captured payload. ```javascript const toleranceInSeconds = 5 * 60 const now = Math.floor(Date.now() / 1000) if (Math.abs(now - Number(svixTimestamp)) > toleranceInSeconds) { throw new Error('Webhook timestamp outside tolerance window') } ``` Never modify the request body before verification. Even reformatting whitespace or reordering keys invalidates the signature. ## Full example Get your signing secret from the BlindPay dashboard: open your instance, go to the **Webhooks** tab, click the ellipsis button next to your endpoint, and select **Get secret**. ```javascript [verify.js] const crypto = require('node:crypto') function verifyWebhook(secret, payload, svixId, svixTimestamp, svixSignature) { const toleranceInSeconds = 5 * 60 const now = Math.floor(Date.now() / 1000) if (Math.abs(now - Number(svixTimestamp)) > toleranceInSeconds) { throw new Error('Webhook timestamp outside tolerance window') } const signedContent = `${svixId}.${svixTimestamp}.${payload}` const secretBytes = Buffer.from(secret.split('_')[1], 'base64') const expectedSignature = crypto .createHmac('sha256', secretBytes) .update(signedContent) .digest('base64') const signatures = svixSignature.split(' ').map(sig => sig.split(',')[1]) const isValid = signatures.some((sig) => { try { return crypto.timingSafeEqual( Buffer.from(sig, 'base64'), Buffer.from(expectedSignature, 'base64'), ) } catch { return false } }) if (!isValid) { throw new Error('Invalid webhook signature') } return true } // Example values const secret = 'whsec_plJ3nmyCDGBKInavdOK15jsl' const payload = '{"webhook_event":"payin.complete","id":"pi_000000000000"}' const svixId = 'msg_loFOjxBNrRLzqYUf' const svixTimestamp = '1731705121' const svixSignature = 'v1,rAvfW3dJ/X/qxhsaXPOyyCGmRKsaKWcsNccKXlIktD0=' verifyWebhook(secret, payload, svixId, svixTimestamp, svixSignature) ``` ```bash [cURL] # Verification happens in your handler code, not at request time. # This shows the raw headers your endpoint receives on every call. curl -X POST https://your-endpoint.example.com/webhooks \ --header 'svix-id: msg_loFOjxBNrRLzqYUf' \ --header 'svix-timestamp: 1731705121' \ --header 'svix-signature: v1,rAvfW3dJ/X/qxhsaXPOyyCGmRKsaKWcsNccKXlIktD0=' \ --data '{"webhook_event":"payin.complete","id":"pi_000000000000"}' ``` ## Retries and replaying events If your endpoint does not respond with a `2xx` status, BlindPay retries delivery with backoff over the following hours. `svix-id` stays the same across retries of the same event, so use it to deduplicate on your side if you process the payload more than once. You can inspect every event BlindPay has sent to your endpoint, including delivery attempts and response codes, and manually replay any event from the dashboard. Go to the **Webhooks** tab, then open **Events dashboard**: Replaying an event resends the exact original payload with a new delivery attempt. It does not create a new business event, so it is safe to use for backfilling a webhook handler you just fixed. ## Related * [Webhooks](/docs/learn/webhooks): create an endpoint and get your signing secret * [Events](/docs/learn/webhooks-events): the full event catalog and payload shapes * [API keys](/docs/learn/api-keys): authenticate the rest of the API alongside your webhook endpoint * [Payin quickstart](/docs/quickstart-payin): see webhooks in a full payment flow * [Payout quickstart](/docs/quickstart-payout): see webhooks in a payout flow --- --- url: /docs/integrations.md description: >- Add stablecoin payments to any AI-built app: Lovable, v0, Bolt, Replit, Codex, Cursor, Claude Code. Copy-paste prompts, MCP server, Agent Skills, and a REST API. --- Ship stablecoin payments in apps built with AI coding tools and agents. BlindPay exposes its full payments API through three surfaces that drop into any AI builder: an **MCP server**, **Agent Skills**, and a **REST API**. ## Pick your builder * **[Lovable](/docs/integrations/lovable)**: add stablecoin payouts and on-ramps to a Lovable app. * **[v0 by Vercel](/docs/integrations/v0)**: generate a Next.js payments flow with BlindPay. * **[Bolt.new](/docs/integrations/bolt)**: wire BlindPay into a StackBlitz Bolt project. * **[Replit](/docs/integrations/replit)**: build with Replit Agent and BlindPay. * **[Codex](/docs/integrations/codex)**: use the BlindPay MCP server and Skills with OpenAI's coding agent. * **[Cursor](/docs/integrations/cursor)**: use the BlindPay MCP server and Skills inside Cursor. * **[Claude Code](/docs/integrations/claude-code)**: move money from the terminal with Claude Code. * **[OpenClaw](/docs/integrations/openclaw)**: let the autonomous agent run payouts via the BlindPay MCP server. * **[Hermes Agent](/docs/integrations/hermes)**: connect the Nous Research agent to BlindPay over MCP. ## The three integration surfaces ### MCP Server The Model Context Protocol server exposes the BlindPay API as tools. Works with Codex, Claude, Cursor, Claude Code, and any MCP-compatible host. ```json [.mcp.json / .cursor/mcp.json] { "mcpServers": { "blindpay": { "command": "npx", "args": ["-y", "@blindpay/mcp"], "env": { "BLINDPAY_API_KEY": "your-api-key", "BLINDPAY_INSTANCE_ID": "your-instance-id" } } } } ``` ### Agent Skills A packaged knowledge layer that teaches any agent BlindPay's rails, corridors, fees, and API patterns. ```bash [Terminal] npx skills add blindpaylabs/blindpay-skills ``` ### REST API For builders that generate code (Lovable, v0, Bolt, Replit), call the REST API directly. ```bash [cURL] curl https://api.blindpay.com/v1/instances/in_000000000000/payouts \ -H "Authorization: Bearer YOUR_SECRET_TOKEN" ``` Get your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). See the [AI page](/docs/build-with-ai) for the full MCP, Skills, and CLI overview. ## Next steps * [Quick start: stablecoin to fiat](/docs/quickstart-payout) * [API reference](https://api.blindpay.com/reference) * [BlindPay for AI agents](/docs/build-with-ai) --- --- url: /docs/integrations/lovable.md description: >- Add stablecoin payments to your Lovable app: payouts, USDC/USDT on-ramp to fiat, and virtual accounts via the BlindPay API and a copy-paste prompt. --- [Lovable](https://lovable.dev) builds full-stack apps from natural language. This guide adds **stablecoin payments**: cross-border payouts, on-ramps, and virtual accounts: to a Lovable project using the BlindPay REST API. ## Copy-paste prompt Paste this into Lovable to scaffold a stablecoin payout flow: ```text [Lovable prompt] Add stablecoin payments to this app using the BlindPay API (https://api.blindpay.com). Requirements: - Create a backend function that calls BlindPay to: 1. Create a payout quote (POST /v1/instances/{instance_id}/payouts/evm/quote) 2. Execute a payout (POST /v1/instances/{instance_id}/payouts/evm) - Read BLINDPAY_API_KEY and BLINDPAY_INSTANCE_ID from secrets. - Authenticate with: Authorization: Bearer ${BLINDPAY_API_KEY} - Build a form where a user enters an amount in USDC and a destination (bank account / blockchain wallet), shows the live quote, and submits the payout. - Never expose the API key in client code: all BlindPay calls go through the backend. Docs: https://blindpay.com/docs/getting-started/overview ``` ## Setup ### Get your credentials Create an account and a development instance, then copy your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). ### Add secrets in Lovable In your Lovable project settings (or connected Supabase), add: ```bash [Secrets] BLINDPAY_API_KEY=your-api-key BLINDPAY_INSTANCE_ID=your-instance-id ``` ### Paste the prompt Paste the prompt above. Lovable generates a backend function plus UI. Review the generated calls against the [API reference](https://api.blindpay.com/reference). ## Example backend call ```ts [Backend function] const res = await fetch( `https://api.blindpay.com/v1/instances/${instanceId}/payouts/evm/quote`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.BLINDPAY_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ currency_type: 'sender', request_amount: 1000 }), }, ) const quote = await res.json() ``` Keep the secret API key server-side. In Lovable that means a backend/edge function: never a client component. ## Next steps * [Quick start: stablecoin to fiat](/docs/quickstart-payout) * [All AI builder integrations](/docs/integrations/) * [BlindPay for AI agents](/docs/build-with-ai) --- --- url: /docs/integrations/v0.md description: >- Add stablecoin payments to a v0 (Vercel) Next.js app: payout, on-ramp, and virtual-account flows via the BlindPay API and a copy-paste prompt. --- [v0](https://v0.dev) by Vercel generates Next.js apps from prompts. This guide adds **stablecoin payments** to a v0 project: payouts, on-ramps, and virtual accounts: via the BlindPay REST API and Next.js route handlers. ## Copy-paste prompt ```text [v0 prompt] Add stablecoin payments to this Next.js app using the BlindPay API. - Create a route handler at app/api/payout/route.ts that POSTs to https://api.blindpay.com/v1/instances/${BLINDPAY_INSTANCE_ID}/payouts/evm/quote and /payouts/evm, authenticating with Authorization: Bearer ${BLINDPAY_API_KEY}. - Read BLINDPAY_API_KEY and BLINDPAY_INSTANCE_ID from process.env (server only). - Build a client form: amount in USDC, destination, a "Get quote" button that calls the route handler, and a "Send payout" button. - Keep the API key server-side in the route handler. Docs: https://blindpay.com/docs/getting-started/overview ``` ## Setup ### Get your credentials Grab your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). ### Add Vercel env vars ```bash [.env.local] BLINDPAY_API_KEY=your-api-key BLINDPAY_INSTANCE_ID=your-instance-id ``` Add the same vars in your Vercel project settings for production. ### Paste the prompt Paste the prompt into v0, then deploy. Verify the generated route against the [API reference](https://api.blindpay.com/reference). ## Example route handler ```ts [app/api/payout/route.ts] export async function POST(req: Request) { const { request_amount } = await req.json() const res = await fetch( `https://api.blindpay.com/v1/instances/${process.env.BLINDPAY_INSTANCE_ID}/payouts/evm/quote`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.BLINDPAY_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ currency_type: 'sender', request_amount }), }, ) return Response.json(await res.json()) } ``` The secret key must only be read in server code (route handlers, server actions). Never ship it to the client. ## Next steps * [Quick start: stablecoin to fiat](/docs/quickstart-payout) * [All AI builder integrations](/docs/integrations/) * [BlindPay for AI agents](/docs/build-with-ai) --- --- url: /docs/integrations/bolt.md description: >- Add stablecoin payments to a Bolt.new (StackBlitz) app: payout, on-ramp, and virtual-account flows via the BlindPay API and a copy-paste prompt. --- [Bolt.new](https://bolt.new) by StackBlitz builds and runs full-stack apps in the browser from a prompt. This guide adds **stablecoin payments**: payouts, on-ramps, and virtual accounts: using the BlindPay REST API. ## Copy-paste prompt ```text [Bolt.new prompt] Add stablecoin payments to this app using the BlindPay API (https://api.blindpay.com). - Create a server route that calls BlindPay to create a payout quote and execute a payout: POST /v1/instances/${BLINDPAY_INSTANCE_ID}/payouts/evm/quote and POST /v1/instances/${BLINDPAY_INSTANCE_ID}/payouts/evm. - Authenticate with Authorization: Bearer ${BLINDPAY_API_KEY}. - Read BLINDPAY_API_KEY and BLINDPAY_INSTANCE_ID from environment variables. - Build a UI: enter a USDC amount and destination, fetch a live quote, send the payout. - Keep the secret key on the server, never in client code. Docs: https://blindpay.com/docs/getting-started/overview ``` ## Setup ### Get your credentials Copy your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). ### Add environment variables ```bash [.env] BLINDPAY_API_KEY=your-api-key BLINDPAY_INSTANCE_ID=your-instance-id ``` ### Paste the prompt Paste the prompt into Bolt.new and run. Check the generated requests against the [API reference](https://api.blindpay.com/reference). ## Example server call ```ts [server route] const res = await fetch( `https://api.blindpay.com/v1/instances/${process.env.BLINDPAY_INSTANCE_ID}/payouts/evm/quote`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.BLINDPAY_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ currency_type: 'sender', request_amount: 1000 }), }, ) const quote = await res.json() ``` Bolt apps run client-side in StackBlitz: route BlindPay calls through a server/edge function so the secret key is never exposed. ## Next steps * [Quick start: stablecoin to fiat](/docs/quickstart-payout) * [All AI builder integrations](/docs/integrations/) * [BlindPay for AI agents](/docs/build-with-ai) --- --- url: /docs/integrations/replit.md description: >- Add stablecoin payments to a Replit app with Replit Agent: payout, on-ramp, and virtual-account flows via the BlindPay API and Replit Secrets. --- [Replit](https://replit.com) and Replit Agent build and host full-stack apps. This guide adds **stablecoin payments**: payouts, on-ramps, and virtual accounts: with the BlindPay REST API and Replit Secrets. ## Copy-paste prompt Give this to Replit Agent: ```text [Replit Agent prompt] Integrate stablecoin payments using the BlindPay API (https://api.blindpay.com). - Add a backend route that creates a payout quote and executes a payout: POST /v1/instances/${BLINDPAY_INSTANCE_ID}/payouts/evm/quote and /payouts/evm. - Authenticate with Authorization: Bearer ${BLINDPAY_API_KEY}. - Read BLINDPAY_API_KEY and BLINDPAY_INSTANCE_ID from Replit Secrets (environment). - Build a UI: enter a USDC amount and destination, show the live quote, send the payout. - Keep the secret key server-side only. Docs: https://blindpay.com/docs/getting-started/overview ``` ## Setup ### Get your credentials Copy your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). ### Add Replit Secrets Open **Tools → Secrets** and add: ```bash [Replit Secrets] BLINDPAY_API_KEY=your-api-key BLINDPAY_INSTANCE_ID=your-instance-id ``` ### Prompt the agent Paste the prompt into Replit Agent. Review the generated route against the [API reference](https://api.blindpay.com/reference). ## Example server call ```ts [server] const res = await fetch( `https://api.blindpay.com/v1/instances/${process.env.BLINDPAY_INSTANCE_ID}/payouts/evm/quote`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.BLINDPAY_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ currency_type: 'sender', request_amount: 1000 }), }, ) const quote = await res.json() ``` Always read the key from Replit Secrets in server code. Never commit it or expose it in the client. ## Next steps * [Quick start: stablecoin to fiat](/docs/quickstart-payout) * [All AI builder integrations](/docs/integrations/) * [BlindPay for AI agents](/docs/build-with-ai) --- --- url: /docs/integrations/codex.md description: >- Connect OpenAI Codex to BlindPay via the MCP server and Agent Skills: run payouts, quotes, and virtual accounts from the terminal or IDE. --- [Codex](https://learn.chatgpt.com/docs/codex/cli) is OpenAI's coding agent for the terminal, IDE, and desktop app. Connect it to BlindPay with the **MCP server** and **Agent Skills** for agentic stablecoin payments while you build. ## Add the MCP server ```bash [Terminal] codex mcp add blindpay \ --env BLINDPAY_API_KEY=your-api-key \ --env BLINDPAY_INSTANCE_ID=your-instance-id \ -- npx -y @blindpay/mcp ``` Run `codex mcp list` to verify that the `blindpay` server is configured. Codex CLI, the IDE extension, and the desktop app share this MCP configuration. ## Add Agent Skills ```bash [Terminal] npx skills add blindpaylabs/blindpay-skills ``` Skills teach Codex BlindPay's rails, corridors, fees, KYC/KYB flows, and API patterns so it can build correct integrations in your codebase. ## Use it ### Get your credentials Copy your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). ### Ask Codex ```text [Codex prompt] Use the BlindPay MCP tools to create a payout quote for 1000 USDC to a USD bank account, show the fees and FX rate, then execute the payout after I confirm. ``` ### Build integrations With Skills loaded, Codex can scaffold full BlindPay integrations in your codebase, not just make one-off tool calls. Codex stores MCP settings in `~/.codex/config.toml`. Keep that file private, and require explicit confirmation before executing any money movement. ## Next steps * [BlindPay for AI agents](/docs/build-with-ai) * [All AI builder integrations](/docs/integrations/) * [API reference](https://api.blindpay.com/reference) --- --- url: /docs/integrations/cursor.md description: >- Connect Cursor to BlindPay via the MCP server and Agent Skills: move money, run payouts, and query corridors in natural language. --- [Cursor](https://cursor.com) is an AI code editor. Connect it to BlindPay with the **MCP server** (live tools) and **Agent Skills** (domain knowledge) for agentic stablecoin payments inside your editor. ## Add the MCP server Create `.cursor/mcp.json` in your project (or add to the global config): ```json [.cursor/mcp.json] { "mcpServers": { "blindpay": { "command": "npx", "args": ["-y", "@blindpay/mcp"], "env": { "BLINDPAY_API_KEY": "your-api-key", "BLINDPAY_INSTANCE_ID": "your-instance-id" } } } } ``` Restart Cursor, then enable the `blindpay` server in **Settings → MCP**. ## Add Agent Skills ```bash [Terminal] npx skills add blindpaylabs/blindpay-skills ``` Skills teach the agent BlindPay's rails, corridors, fees, KYC/KYB flows, and API patterns so it writes correct integrations. ## Use it ### Get your credentials Copy your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). ### Ask the agent In Cursor chat, try: ```text [Cursor prompt] Using the BlindPay MCP tools, create a payout quote for 1000 USDC to a USD bank account, then show me the fees and FX rate before executing. ``` ### Build the integration Ask Cursor to scaffold the integration in your app: it has both live tools and Skills context. The MCP server runs locally and reads your key from the config env. Keep `.cursor/mcp.json` out of version control if it contains live credentials. ## Next steps * [BlindPay for AI agents](/docs/build-with-ai) * [All AI builder integrations](/docs/integrations/) * [API reference](https://api.blindpay.com/reference) --- --- url: /docs/integrations/claude-code.md description: >- Connect Claude Code to BlindPay via the MCP server and Agent Skills: run payouts, quotes, and virtual accounts from the terminal in natural language. --- [Claude Code](https://claude.com/claude-code) is Anthropic's terminal coding agent. Connect it to BlindPay with the **MCP server** and **Agent Skills** for agentic stablecoin payments from the command line. ## Add the MCP server ```bash [Terminal] claude mcp add blindpay \ --env BLINDPAY_API_KEY=your-api-key \ --env BLINDPAY_INSTANCE_ID=your-instance-id \ -- npx -y @blindpay/mcp ``` Or add it manually to your project's `.mcp.json`: ```json [.mcp.json] { "mcpServers": { "blindpay": { "command": "npx", "args": ["-y", "@blindpay/mcp"], "env": { "BLINDPAY_API_KEY": "your-api-key", "BLINDPAY_INSTANCE_ID": "your-instance-id" } } } } ``` ## Add Agent Skills ```bash [Terminal] npx skills add blindpaylabs/blindpay-skills ``` ## Use it ### Get your credentials Copy your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). ### Ask Claude Code ```text [Claude Code prompt] Use the BlindPay MCP tools to create a payout quote for 1000 USDC to a USD bank account, show the fees and FX rate, then execute the payout after I confirm. ``` ### Build integrations With Skills loaded, Claude Code can scaffold full BlindPay integrations in your codebase, not just one-off calls. The MCP server reads your key from the env you pass to `claude mcp add`. Money-movement tools should require explicit confirmation before executing. ## Next steps * [BlindPay for AI agents](/docs/build-with-ai) * [All AI builder integrations](/docs/integrations/) * [API reference](https://api.blindpay.com/reference) --- --- url: /docs/integrations/openclaw.md description: >- Connect OpenClaw to BlindPay with the MCP server and Agent Skills. The autonomous agent runs stablecoin payouts and quotes via natural language. --- [OpenClaw](https://openclaw.ai) is an open-source autonomous AI agent that runs locally and makes sequential tool calls to get work done. Connect it to BlindPay with the **MCP server** and **Agent Skills** for agentic stablecoin payments. ## Add the MCP server Install BlindPay's MCP server through OpenClaw's MCP registry: ```bash [Terminal] openclaw mcp add blindpay \ --command npx \ --arg -y \ --arg @blindpay/mcp \ --env BLINDPAY_API_KEY=your-api-key \ --env BLINDPAY_INSTANCE_ID=your-instance-id ``` Or add it directly under `mcp.servers` in your OpenClaw config: ```json [OpenClaw config] { "mcp": { "servers": { "blindpay": { "command": "npx", "args": ["-y", "@blindpay/mcp"], "env": { "BLINDPAY_API_KEY": "your-api-key", "BLINDPAY_INSTANCE_ID": "your-instance-id" } } } } } ``` Verify the server is reachable: ```bash [Terminal] openclaw mcp doctor blindpay --probe ``` ## Add Agent Skills ```bash [Terminal] npx skills add blindpaylabs/blindpay-skills ``` ## Use it ### Get your credentials Copy your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). ### Add the MCP server Run the `openclaw mcp add` command above (or edit the config), then reload. ### Ask the agent Tell OpenClaw: ```text [OpenClaw prompt] Use the BlindPay MCP tools to create a payout quote for 1000 USDC to a USD bank account, show the fees and FX rate, then execute the payout after I confirm. ``` OpenClaw runs autonomously and chains tool calls. Keep money-movement gated on explicit confirmation, and scope the API key to what the agent should do. ## Next steps * [BlindPay for AI agents](/docs/build-with-ai) * [All AI builder integrations](/docs/integrations/) * [API reference](https://api.blindpay.com/reference) --- --- url: /docs/integrations/hermes.md description: >- Connect Hermes Agent to BlindPay with the MCP server and Agent Skills. The Nous Research agent runs stablecoin payouts and quotes via natural language. --- [Hermes Agent](https://hermes-agent.nousresearch.com) by Nous Research is a self-hosted AI agent with native MCP support. Connect it to BlindPay with the **MCP server** and **Agent Skills** for agentic stablecoin payments. ## Add the MCP server Add BlindPay under `mcp_servers` in `~/.hermes/config.yaml`: ```yaml [~/.hermes/config.yaml] mcp_servers: blindpay: command: "npx" args: ["-y", "@blindpay/mcp"] env: BLINDPAY_API_KEY: "your-api-key" BLINDPAY_INSTANCE_ID: "your-instance-id" ``` Then reload MCP from within a Hermes session: ```text [Hermes session] /reload-mcp ``` ## Add Agent Skills ```bash [Terminal] npx skills add blindpaylabs/blindpay-skills ``` ## Use it ### Get your credentials Copy your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com/sign-up). ### Add the MCP server Add the `blindpay` block to `~/.hermes/config.yaml`, then run `/reload-mcp`. ### Ask the agent Tell Hermes: ```text [Hermes prompt] Use the BlindPay MCP tools to create a payout quote for 1000 USDC to a USD bank account, show the fees and FX rate, then execute the payout after I confirm. ``` Hermes writes reusable skills as it works and runs continuously. Keep money-movement gated on explicit confirmation, and scope the API key to what the agent should do. ## Next steps * [BlindPay for AI agents](/docs/build-with-ai) * [All AI builder integrations](/docs/integrations/) * [API reference](https://api.blindpay.com/reference) --- --- url: /docs/mint-usdb.md description: >- Mint BlindPay's USDB test stablecoin on EVM testnets, Stellar Testnet, and Solana Devnet to simulate payments on development instances. --- USDB is BlindPay's test stablecoin. It only exists on development instances, across every development network, and you can mint as much as you need to simulate payins, payouts, and transfers. USDB is a development-only token. Production instances only accept `USDC` or `USDT`, never USDB. ## Where you can mint | Network | How | | --- | --- | | EVM testnets (`sepolia`, `base_sepolia`, `arbitrum_sepolia`, `polygon_amoy`) | Dashboard mint utility, to a connected browser wallet | | Stellar Testnet (`stellar_testnet`) | REST endpoints: create a trustline, then mint | | Solana Devnet (`solana_devnet`) | REST endpoint, mints to any address | The Solana endpoint is the only one that mints to an arbitrary address, which makes it the easiest way to fund a [managed wallet](/docs/wallets) with test tokens: create the wallet on `solana_devnet` and mint straight to its address. ## Mint on EVM chains USDB on EVM chains is minted from the dashboard, not the API. The wallet needs testnet ether to pay gas. Get some from the [Base Sepolia faucet](https://www.alchemy.com/faucets/base-sepolia). Open `https://app.blindpay.com/instances/{instance_id}/utilities/mint` in your browser, replace `{instance_id}` with your instance ID, and mint USDB on `Base Sepolia`. Use the same wallet address you registered as a [blockchain wallet](/docs/blockchain-wallets). The dashboard mints to the wallet connected in your browser, double check you're minting on the correct network. ## Mint on Stellar USDB can also be minted on `Stellar Testnet`. Stellar Testnet USDB issuer: `GCQSSIMOW5OCGULZATDXKU5MOJBOMFX6G65X6CXZDQ7AIB3SKFUZ67NX` ### Create an asset trustline (one time only) Before your wallet can receive USDB, it needs a trustline to the USDB asset. Do this once per wallet. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/create-asset-trustline \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "address": "YOUR_ADDRESS" }' ``` This returns an unsigned XDR transaction: ```json { "success": true, "xdr": "AAAAA..." } ``` ### Sign and submit the trustline transaction You have two options, pick one: * **Sign and submit it yourself**: sign the XDR with your Stellar wallet (or [Stellar Lab](https://lab.stellar.org/transaction/sign)) and submit it to the network directly. * **Let BlindPay submit it**: sign the XDR with your Stellar wallet, then pass the resulting `signedXdr` to the mint endpoint in the next step and BlindPay submits the trustline transaction for you. ### Mint USDB tokens Once the trustline exists, mint USDB to your wallet address. Pass `signedXdr` only if you chose the second option above. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/mint-usdb-stellar \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "address": "YOUR_WALLET_ADDRESS", "amount": "1000000000000000000", "signedXdr": "YOUR_SIGNED_XDR" }' ``` ## Mint on Solana USDB can also be minted on `Solana Devnet`. Pass the destination `address` and the `amount`: ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/mint-usdb-solana \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "address": "YOUR_WALLET_ADDRESS", "amount": "100" }' ``` This returns the on-chain signature: ```json { "success": true, "signature": "5xY..." } ``` ## Related * [Sandbox vs. production](/docs/learn/sandbox-vs-production): how development instances simulate settlement * [Managed wallets](/docs/wallets): BlindPay-custodied wallets you can mint into on Solana Devnet * [Blockchain wallets](/docs/blockchain-wallets): the external wallets the EVM and Stellar mints target * [Payout with EVM](/docs/payout-evm): spend the minted USDB in an off-ramp payout * [Supported chains](/docs/kb/supported-chains): the full chain and token matrix --- --- url: /docs/send.md description: >- Send stablecoins out of a BlindPay-managed wallet to another managed wallet or any external address, using the BlindPay API. --- Send covers moving stablecoins out of a [managed wallet](/docs/wallets) (`bl_...`): to another managed wallet, or to any external blockchain address. There is no fiat leg; both sides of the move are stablecoin, and because BlindPay custodies the source wallet, it's all REST calls with no signing. If you want the funds to leave as fiat in a bank account instead, that's a [payout](/docs/payouts). Transfers are in beta. USDC transfers can move across chains (Ethereum, Polygon, Base, Arbitrum) using [Circle CCTP v2](/docs/transfer-quotes#cross-chain-usdc-transfers-circle-cctp-v2); every other token still requires the destination network to match the source wallet's. There is no token conversion. ## How it works Every transfer follows the same two-step flow as a payin or payout: create a quote, then execute it. | Step | Call | Purpose | | --- | --- | --- | | 1 | Create a [transfer quote](/docs/transfer-quotes) | Lock in the amount, token, and network for the move | | 2 | Execute the [transfer](/docs/transfers) | Consume the quote and send the stablecoins | * **Source**: a managed wallet (`wallet_id`, `bl_...`) that holds the stablecoin balance * **Destination**: another managed wallet's address, or any external blockchain address the receiver controls * **Token and network**: must be identical on both sides, except a USDC transfer can cross chains via Circle CCTP v2 (Ethereum, Polygon, Base, Arbitrum) Transfer quotes are consumed the same way payin and payout quotes are: create one, then execute against its ID before it expires. Because there is no fiat conversion involved, the quote step exists mainly to lock in the amount and destination, not to price an exchange rate. The transfer quote expires in 15 seconds, the shortest of any BlindPay quote. ## In this section * [Transfer quotes](/docs/transfer-quotes): create a quote that locks in the source wallet, destination, token, network, and amount * [Transfers](/docs/transfers): execute a transfer against a quote and track its status ## Related * [Managed wallets](/docs/wallets): create and fund the source wallet for a transfer * [Receive](/docs/receive): the other direction, stablecoins arriving in a managed wallet * [Payouts](/docs/payouts): convert stablecoins held in a wallet to fiat instead of moving them on-chain * [Supported chains](/docs/kb/supported-chains): chain and token support matrix --- --- url: /docs/transfer-quotes.md description: >- Create a transfer quote to lock in the source wallet, destination address, token, network, and amount before executing a transfer. --- A transfer quote locks in the details of a stablecoin move before you execute it: the source managed wallet, the destination address, the token, the network, and the amount. Executing the [transfer](/docs/transfers) simply consumes this quote, it does not take any new parameters of its own. Transfers are in beta. `customer_token` must always match `sender_token`, there is no token conversion. `customer_network` must match the source wallet's own network, except when both tokens are `USDC`: a USDC transfer can additionally cross chains using [Circle CCTP v2](#cross-chain-usdc-transfers-circle-cctp-v2). ## How it works * **Source**: a managed wallet (`wallet_id`, `bl_...`) that holds the stablecoin balance. This is the only supported source, unlike payouts, which can also draw from an external blockchain wallet. * **Destination**: any blockchain address, `customer_wallet_address`, normally on the same network as the source wallet; a USDC transfer can target a different [CCTP v2 network](#cross-chain-usdc-transfers-circle-cctp-v2) instead. It does not need to belong to a BlindPay customer or wallet, it can be any address the recipient controls. * **Expiry is very short**. A transfer quote is meant to be executed immediately after creation, not held for later use. Read `expires_at` from the response rather than assuming a fixed window, and call [create a transfer](/docs/transfers) right after creating the quote. `expires_at` is returned in epoch milliseconds. ## Cross-chain USDC transfers (Circle CCTP v2) When both `sender_token` and `customer_token` are `USDC`, `customer_network` can be a different network than the source wallet's. BlindPay bridges the move using [Circle's Cross-Chain Transfer Protocol v2](https://developers.circle.com/cctp): USDC is burned on the source chain and an equivalent amount is minted on the destination chain once Circle attests the burn. Supported networks are Ethereum, Polygon, Base, and Arbitrum on a production instance; on a development instance, the matching testnets (Ethereum Sepolia, Polygon Amoy, Base Sepolia, Arbitrum Sepolia), using real testnet USDC rather than USDB, since CCTP can only move native USDC. Both the source and destination network must be on the same side of that mainnet/testnet boundary. Requesting a token other than USDC across networks returns `cross_chain_transfers_only_support_usdc`; requesting a network pair CCTP v2 does not support returns `cctp_route_not_supported`. The quote itself is unaffected by the bridge: amounts stay 1:1 and BlindPay adds no fee on top, exactly like a same-network transfer. Circle's protocol deducts its own small fee (typically a fraction of a cent up to a few cents, a few basis points of the amount) from the amount minted on the destination chain; this is not reflected in `receiver_amount` or any other quote field. Cross-chain transfers typically settle in 8 to 20 seconds; transfers sourced from Polygon settle in around 8 seconds either way. Solana, Stellar, and Tron are not part of cross-chain transfers yet; same-network moves on those chains are unaffected. ## Prerequisites You also need a [customer](/docs/kb/kyc) with `kyc_status: "approved"` and a [managed wallet](/docs/wallets) holding the stablecoin balance you want to move. ## Request fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `wallet_id` | string (`bl_...`) | yes | The managed wallet the funds move out of. Must belong to your instance and not be deleted. | | `amount_reference` | enum | yes | `sender` or `receiver`. Since sender and receiver amounts are always equal for transfers today, this only affects which field you read the amount from in your own bookkeeping. | | `request_amount` | integer | yes | Amount in minor units, no floats. `$100.00` sends as `10000`. Minimum is `1`. | | `sender_token` | enum | yes | `USDC`, `USDT`, or `USDB`. Must be a token allowed on the source wallet's network. | | `customer_token` | enum | yes | Must equal `sender_token`. | | `customer_network` | enum | yes | Must equal the source wallet's own network, unless `sender_token` and `customer_token` are both `USDC` and the pair is a [supported CCTP v2 route](#cross-chain-usdc-transfers-circle-cctp-v2). | | `customer_wallet_address` | string | yes | The destination blockchain address, 32 to 64 characters, validated for the target network. Can be another wallet you created or any external address. | | `cover_fees` | boolean | yes | Accepted for API consistency with payin and payout quotes, but fee math is not yet active for transfers: `sender_amount` and `receiver_amount` are always equal to `request_amount`. | | `partner_fee_id` | string (`pf_...`) | no | Accepted, but partner fee amounts are not yet computed for transfers. | USDT transfers are currently restricted to the Polygon network only, a tighter limit than payins and payouts allow for USDT. ## Create a transfer quote ```bash [cURL] curl https://api.blindpay.com/v1/instances/in_000000000000/transfer-quotes \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "wallet_id": "bl_000000000000", "amount_reference": "sender", "cover_fees": true, "request_amount": 1000, "sender_token": "USDC", "customer_wallet_address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372", "customer_token": "USDC", "customer_network": "polygon", "partner_fee_id": "pf_000000000000" }' ``` ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/transfer-quotes', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer YOUR_API_KEY', }, body: JSON.stringify({ wallet_id: 'bl_000000000000', amount_reference: 'sender', cover_fees: true, request_amount: 1000, sender_token: 'USDC', customer_wallet_address: '0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372', customer_token: 'USDC', customer_network: 'polygon', partner_fee_id: 'pf_000000000000', }), }, ) const quote = await response.json() ``` ## Response ```json { "id": "qu_000000000000", "expires_at": 1712958191000, "commercial_quotation": 100, "blindpay_quotation": 100, "receiver_amount": 1000, "sender_amount": 1000, "partner_fee_amount": 0, "flat_fee": 0 } ``` | Field | Type | Notes | | --- | --- | --- | | `id` | string (`qu_...`) | Pass this as `transfer_quote_id` when you create the transfer. | | `expires_at` | number | Epoch milliseconds. Execute the transfer before this passes. | | `commercial_quotation` | number | Rate preview. Currently fixed at `100` for transfers since there is no FX conversion, even for a cross-chain USDC move. | | `blindpay_quotation` | number | Same as `commercial_quotation` for transfers today. | | `receiver_amount` | number | Equal to `request_amount`. | | `sender_amount` | number | Equal to `request_amount`. | | `partner_fee_amount` | number | Always `0` for transfers today, even if `partner_fee_id` was set. | | `flat_fee` | number | Always `0` for transfers today. | The `qu_` prefix is shared across payin quotes, payout quotes, and transfer quotes, you can't tell which kind of quote an ID refers to just by looking at it. ## Validation order The API checks, in this order: the caller has permission to create transactions, the wallet exists and belongs to your instance. If `customer_network` differs from the source wallet's network, the API requires both `sender_token` and `customer_token` to be `USDC` (`cross_chain_transfers_only_support_usdc` otherwise) and the network pair to be a [supported CCTP v2 route](#cross-chain-usdc-transfers-circle-cctp-v2) (`cctp_route_not_supported` otherwise). If `customer_network` matches the source wallet's network, the API instead checks that the token and network are allowed for your instance type, that USDT is only used on Polygon, and that `sender_token` matches `customer_token`. Either path finishes with a check that the receiving customer's KYC is approved. The first failing check is the one returned. ## Testing There is no dedicated test-amount sentinel for transfer quotes or transfers (unlike payins and payouts, which force outcomes at `$666.00` and `$777.00`). On development instances, transfer quotes go through the same token and network rules as production, restricted to the development tokens and testnets described in [supported chains](/docs/kb/supported-chains). Cross-chain USDC quotes are the exception: on a development instance they use real testnet USDC, not USDB, on the CCTP v2 testnets (Ethereum Sepolia, Polygon Amoy, Base Sepolia, Arbitrum Sepolia), since Circle's protocol can only move native USDC. ## Related * [Send](/docs/send): the concept overview and beta scope for transfers * [Transfers](/docs/transfers): execute a transfer against this quote * [Managed wallets](/docs/wallets): create and fund the source wallet * [Payout quotes](/docs/payout-quotes): compare against the quote used for off-ramp payouts * [Supported chains](/docs/kb/supported-chains): token and network support matrix --- --- url: /docs/transfers.md description: >- Execute a stablecoin transfer from a transfer quote and track it through to completion with the BlindPay API. --- A transfer is the object that actually moves stablecoins: it consumes a [transfer quote](/docs/transfer-quotes) and sends the funds from a managed wallet to the destination address locked in on that quote. The transfer itself takes a single field; the amount, token, network, and destination were already set when you created the quote. Transfers are in beta. USDC moves can now cross chains between Ethereum, Polygon, Base, and Arbitrum using [Circle CCTP v2](/docs/transfer-quotes#cross-chain-usdc-transfers-circle-cctp-v2); every other token still requires the destination network to match the source wallet's exactly. ## How it works ``` transfer quote (qu_...) -> execute the transfer -> stablecoins sent from the managed wallet -> destination confirms receipt ``` A transfer quote expires 5 minutes after creation, so execute the transfer before then. Once created, a transfer cannot be canceled. You also need a [managed wallet](/docs/wallets) (`bl_...`) and a [transfer quote](/docs/transfer-quotes) (`qu_...`). ## Execute a transfer Replace `qu_000000000000` with the transfer quote ID you created previously. ```bash [cURL] curl https://api.blindpay.com/v1/instances/in_000000000000/transfers \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "transfer_quote_id": "qu_000000000000" }' ``` ### Response ```json { "id": "tr_000000000000", "status": "processing", "tracking_bridge_swap": { "step": "on_hold" }, "tracking_complete": { "step": "on_hold" }, "tracking_paymaster": { "step": "on_hold" }, "tracking_transaction_monitoring": { "step": "on_hold" }, "tracking_partner_fee": { "step": "on_hold" } } ``` ## Status lifecycle | `status` | Meaning | Terminal? | | --- | --- | --- | | `processing` | The stablecoin send has been submitted and is waiting for confirmation | no | | `completed` | The transfer confirmed and the destination received the stablecoins | yes | Check `tracking_complete` alongside `status` when building a detailed view: it carries additional detail about the send once the transfer confirms. For a cross-chain USDC transfer, `tracking_bridge_swap` tracks the burn on the source chain and `tracking_complete` tracks the mint on the destination chain. A same-network transfer only ever uses `tracking_complete`. ## Testing Transfers do not use the `66600`/`77700` sentinel amounts that payins and payouts support. On a development instance, a same-network transfer executes against the sandbox `USDB` token on the corresponding testnet and confirms once the network finalizes the send. A cross-chain USDC transfer is the exception: it uses real testnet USDC, not USDB, since [Circle CCTP v2](/docs/transfer-quotes#cross-chain-usdc-transfers-circle-cctp-v2) can only move native USDC. ## Webhooks | Event | Fires when | | --- | --- | | `transfer.new` | The transfer is created and the stablecoin send has been submitted | | `transfer.complete` | The transfer confirms and the destination has received the stablecoins | See [webhooks](/docs/learn/webhooks) for signature verification and full payload details. ## Related * [Transfer quotes](/docs/transfer-quotes): lock in the amount, token, network, and destination before executing a transfer * [Send](/docs/send): the transfer concept and how it fits alongside payins and payouts * [Managed wallets](/docs/wallets): create and fund the source wallet for a transfer * [Blockchain wallets](/docs/blockchain-wallets): register an external destination address --- --- url: /docs/receive.md description: >- Receive stablecoins into a BlindPay-managed wallet and track every deposit through the wallet.inbound webhook. --- Receive covers stablecoins arriving in a [managed wallet](/docs/wallets). There is nothing to call: share the wallet's `address` with whoever is sending, and any transfer that arrives on the matching network credits the wallet. Your integration work is on the webhook side, reacting when funds land. If you're looking for fiat coming in (a bank transfer converted to stablecoins), that's a [payin](/docs/payins). ## How it works 1. Create a [managed wallet](/docs/wallets) for the customer and share its `address`. 2. The sender transfers stablecoins to that address from any wallet or exchange, on the wallet's network. 3. The deposit credits the wallet balance automatically; there is no approval or signature step on the receiving end. 4. BlindPay fires a `wallet.inbound` webhook so your system can react in real time. The deposit must arrive on the same network the wallet was created on. Funds sent on the wrong network are not credited and cannot be recovered by BlindPay. ## The wallet.inbound webhook Every time stablecoins land in a managed wallet, `wallet.inbound` fires with this payload: ```json [wallet.inbound] { "id": "bl_000000000000", "address": "0x1234567890abcdef1234567890abcdef12345678", "network": "base", "token": { "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "id": "usdc", "symbol": "USDC", "amount": 100 } } ``` `wallet.inbound` only fires for USDC and USDT deposits. See [webhooks](/docs/learn/webhooks) for endpoint setup and signature verification. `wallet.inbound` reports `amount` scaled by 100 (so `100` means $1.00), while the wallet balance endpoint reports the raw amount. Don't assume the two use the same unit. ## Check the balance Webhooks are the push signal; the balance endpoint is the pull: ```bash [cURL] curl --request GET \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/wallets/bl_000000000000/balance \ --header 'Authorization: Bearer YOUR_API_KEY' ``` ## Related * [Managed wallets](/docs/wallets): create the wallet that receives the deposits * [Payins](/docs/payins): receive fiat and have it delivered as stablecoins instead * [Send](/docs/send): move the received stablecoins to another wallet * [Payouts](/docs/payouts): convert the balance to fiat in a bank account * [Webhooks](/docs/learn/webhooks): endpoint setup and signature verification --- --- url: /docs/payout-managed-wallet.md description: >- Fund a payout from a BlindPay-managed wallet with a single REST call, no on-chain approval or signing involved. --- This is the simplest way to fund a [payout](/docs/payouts): pull the stablecoins from a [managed wallet](/docs/wallets). BlindPay already custodies the balance, so there is no `approve` call, no signed transaction, and no delegation. You create a payout quote and execute the payout, two REST calls. If the funds live in a wallet you or your customer controls instead, see the blockchain wallet tutorials: [EVM](/docs/payout-evm), [Stellar](/docs/payout-stellar), or [Solana](/docs/payout-solana). ## Prerequisites You also need: * A [customer](/docs/learn/customers) with `kyc_status: "approved"` * A [bank account](/docs/bank-accounts) (`ba_...`) as the payout destination * A [managed wallet](/docs/wallets) (`bl_...`) holding enough stablecoins to cover the payout On a development instance, the easiest way to fund the wallet is to create it on `solana_devnet` and [mint USDB](/docs/mint-usdb#mint-on-solana) straight to its address. ### Create a payout quote A quote locks the conversion rate and fees for 5 minutes. The `network` and `token` describe the funding wallet: match them to the managed wallet's network and the token it holds. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "bank_account_id": "ba_000000000000", "currency_type": "sender", "cover_fees": false, "request_amount": 5000, "network": "solana_devnet", "token": "USDB" }' ``` `request_amount` is an integer in minor units, so `5000` here means $50.00. Save the quote ID (`qu_...`). The response also includes a `contract` object with approval data; you can ignore it, it only matters for external wallets. ### Execute the payout Execute the payout by passing the quote ID and the managed wallet's `address` as the funding source. Because BlindPay custodies the wallet, this single call moves the funds. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "YOUR_MANAGED_WALLET_ADDRESS" }' ``` The response returns the payout with `status: "processing"`. On a development instance it completes automatically a few seconds later and fires a `payout.complete` webhook. The endpoint is `/payouts/evm` regardless of the managed wallet's network; it handles managed wallets on every supported chain, including Solana. That's a complete payout. To send another, create a new quote and execute again; each quote is single-use. ## Related * [Payouts](/docs/payouts): status lifecycle, cover\_fees, testing sentinels, and webhooks * [Managed wallets](/docs/wallets): the funding wallet entity, including balance and inbound webhooks * [Payout quotes](/docs/payout-quotes): full request and response fields * [Payout with EVM](/docs/payout-evm): the external-wallet alternative on EVM chains --- --- url: /docs/payout-evm.md description: >- Fund a payout from an external EVM wallet, approve the ERC-20 token transfer, then execute the payout. --- This tutorial funds a [payout](/docs/payouts) from an external [blockchain wallet](/docs/blockchain-wallets) on an EVM chain (Ethereum, Base, Polygon, Arbitrum). All stablecoins on EVM chains are ERC-20 tokens, so authorization means calling `approve` on the token contract to let BlindPay pull the quoted amount from the sender's wallet. If the funds live in a BlindPay-custodied wallet instead, skip the approval entirely: see [Payout with managed wallet](/docs/payout-managed-wallet). The examples below use `base_sepolia` and `USDB` since they're the development network and test token. Swap in the matching production network and token (`USDC` or `USDT`) when you go live. ## Prerequisites You also need: 1. A [customer](/docs/learn/customers) with `kyc_status: "approved"` and a [bank account](/docs/bank-accounts) (`ba_...`) 2. [An RPC provider URL for Base Sepolia](https://chainlist.org/?search=base\&testnets=true) 3. [A wallet funded with testnet ether](https://www.alchemy.com/faucets/base-sepolia) to pay gas 4. [The private key, or any way to instantiate the wallet with ethers.js](https://docs.ethers.org/v6/api/wallet/#BaseWallet_new) ### Create a quote and approve the tokens The ERC-20 token contract address, ABI, and BlindPay's contract address are all returned in the [payout quote](/docs/payout-quotes) response, so quote and approval fit naturally in one script. This example uses an [Express](https://expressjs.com/en/starter/hello-world.html) server and [ethers.js](https://docs.ethers.org/v6/). Make sure [Node.js](https://nodejs.org/en/download/) is installed. Create a folder, initialize it, and install the dependencies: ```bash npm init -y npm install express ethers ``` Create `index.js` and replace the placeholder values with your own: ```js [index.js] import express from 'express' import { ethers } from 'ethers' const app = express() app.get('/', async (req, res) => { const rpcProviderUrl = '' // Get one at https://chainlist.org/?search=base&testnets=true const walletPrivateKey = '' // This wallet needs testnet ether and USDB to run the transactions below const instanceId = '' const blindpayApiKey = 'YOUR_API_KEY' const bankAccountId = 'ba_000000000000' const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${blindpayApiKey}`, } // 1. Create a payout quote const fiftyDollars = 5000 const quoteBody = { bank_account_id: bankAccountId, currency_type: 'sender', cover_fees: false, request_amount: fiftyDollars, network: 'base_sepolia', // also "sepolia", "arbitrum_sepolia", "polygon_amoy" token: 'USDB', // development instances only ever use USDB } const createQuote = await fetch( `https://api.blindpay.com/v1/instances/${instanceId}/quotes`, { headers, method: 'POST', body: JSON.stringify(quoteBody) } ) const quoteResponse = await createQuote.json() // 2. Approve the tokens const provider = new ethers.JsonRpcProvider( rpcProviderUrl, quoteResponse.contract.network ) const yourWallet = new ethers.Wallet(walletPrivateKey, provider) const contract = new ethers.Contract( quoteResponse.contract.address, quoteResponse.contract.abi, provider ) const contractSigner = contract.connect(yourWallet) const result = await contractSigner.approve( quoteResponse.contract.blindpayContractAddress, quoteResponse.contract.amount ) res.send({ hash: result?.hash, quoteId: quoteResponse.id, }) }) app.listen(3000) console.log('Express started on port 3000') ``` Run it: ```bash node index.js ``` Visit `http://localhost:3000`. After a few seconds you should see a response like: ```json { "hash": "0x1ab66830a4804d80251f01b9d31c054a42068f5783c80d165507cddce0ac78ca", "quoteId": "qu_lxrCXUOOyrem" } ``` ### Execute the payout Once the approval transaction confirms, create the payout with the same `quote_id`. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "YOUR_WALLET_ADDRESS" }' ``` ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ quote_id: 'qu_000000000000', sender_wallet_address: 'YOUR_WALLET_ADDRESS', }), } ) const payout = await response.json() ``` That's a completed payout on EVM. To send another, create a new quote and approve again, the quote and approval are single-use. ## Related * [Payouts](/docs/payouts): status lifecycle, cover\_fees, testing sentinels, and webhooks * [Payout quotes](/docs/payout-quotes): the `contract` object the approval step consumes * [Payout with managed wallet](/docs/payout-managed-wallet): the no-signing alternative * [Mint USDB](/docs/mint-usdb): fund your test wallet on development instances --- --- url: /docs/payout-stellar.md description: >- Fund a payout from an external Stellar wallet, authorize the payout, sign the XDR transaction, then create the payout. --- This tutorial funds a [payout](/docs/payouts) from an external [blockchain wallet](/docs/blockchain-wallets) on Stellar. Stellar has no allowance mechanism, so the client constructs and signs a real payment transaction to BlindPay's treasury address: authorize, sign, then create the payout. BlindPay's Stellar mainnet treasury address: `GCOSSQDM2SWMHRP7CDBOLL2V45NHCRLUWUCEHPPBA2ABCOOLPOLZKIHE`. This is the address that receives payout crypto and sends payin crypto on Stellar mainnet. The examples below use `stellar_testnet` and `USDB` since they're the development network and test token. Swap in the production network and token (`USDC`) when you go live. ## Prerequisites You also need a [customer](/docs/learn/customers) with `kyc_status: "approved"`, a [bank account](/docs/bank-accounts) (`ba_...`), and an unexpired [payout quote](/docs/payout-quotes) (`qu_...`) created with a Stellar `network`. ### Authorize the payout Call the authorize endpoint with the quote and the sender's wallet address. This does not consume the quote or create any record, it only returns an unsigned transaction hash for the client to sign. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/stellar/authorize \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "YOUR_WALLET_ADDRESS" }' ``` The response is the unsigned transaction, ready to sign: ```json { "transaction_hash": "AAA...AAA" } ``` ### Sign and submit the transaction Install the Stellar SDK: ```bash npm install @stellar/stellar-sdk ``` Sign the returned transaction with the sender's Stellar key and submit it to the network: ```js [index.js] import { Horizon, Keypair, Networks, TransactionBuilder, } from '@stellar/stellar-sdk' const server = new Horizon.Server('https://horizon-testnet.stellar.org') const sourceKeypair = Keypair.fromSecret(process.env.STELLAR_SECRET_KEY) // Rebuild the transaction returned by the authorize endpoint const transaction = TransactionBuilder.fromXDR( transactionHash, Networks.TESTNET ) // Sign it with the sender's key transaction.sign(sourceKeypair) // Submit it to Stellar const result = await server.submitTransaction(transaction) console.log(result.hash) // Save result.hash, you need it to create the payout on BlindPay ``` ### Create the payout Create the payout with the quote, the sender's wallet address, and the signed transaction from the previous step. BlindPay independently re-validates the signed transaction against the quote (destination and amount) before dispatching the payout. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/stellar \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "signed_transaction": "YOUR_SIGNED_TRANSACTION", "sender_wallet_address": "YOUR_WALLET_ADDRESS" }' ``` That's a completed payout on Stellar. To send another, create a new quote and repeat the authorize, sign, and create steps, the quote and signed transaction are single-use. ## Related * [Payouts](/docs/payouts): status lifecycle, cover\_fees, testing sentinels, and webhooks * [Payout quotes](/docs/payout-quotes): full request and response fields * [Mint USDB](/docs/mint-usdb): mint on Stellar Testnet, including the one-time trustline * [Payout with managed wallet](/docs/payout-managed-wallet): the no-signing alternative (Stellar not yet supported for managed wallets) --- --- url: /docs/payout-solana.md description: >- Fund a payout from an external Solana wallet, delegate the tokens to BlindPay, then create the payout. --- This tutorial funds a [payout](/docs/payouts) from an external [blockchain wallet](/docs/blockchain-wallets) on Solana. Solana payouts need a token delegation before the payout executes: the sender delegates the quoted amount to BlindPay, then BlindPay pulls it during payout processing. If the funds live in a BlindPay-custodied wallet instead, skip the delegation entirely: see [Payout with managed wallet](/docs/payout-managed-wallet). The examples below use `solana_devnet` and `USDB` since they're the development network and test token. Swap in the production network and token (`USDC` or `USDT`) when you go live. ## Prerequisites You also need a [customer](/docs/learn/customers) with `kyc_status: "approved"`, a [bank account](/docs/bank-accounts) (`ba_...`), and an unexpired [payout quote](/docs/payout-quotes) (`qu_...`) created with a Solana `network`. ### Prepare the delegation transaction ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/prepare-delegate-solana \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "owner_address": "YOUR_SOLANA_WALLET_ADDRESS", "token_address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "amount": "50000000" }' ``` This returns a serialized transaction to sign: ```json { "success": true, "transaction": "AQAAA..." } ``` ### Sign and submit the delegation Install the Solana dependencies: ```bash npm install @solana/web3.js bs58 ``` ```js [index.js] import { Connection, VersionedTransaction } from '@solana/web3.js' import { Buffer } from 'node:buffer' const RPC_URL = 'https://api.devnet.solana.com' // Use a mainnet RPC in production const connection = new Connection(RPC_URL, 'confirmed') // Serialized transaction from the previous step const serializedTransaction = 'AQAAA...' async function signAndSubmitDelegation() { const transactionBuffer = Buffer.from(serializedTransaction, 'base64') const transaction = VersionedTransaction.deserialize(transactionBuffer) // Sign with the sender's wallet (a browser wallet like Phantom, or a local Keypair server-side) const signedTransaction = await window.solana.signTransaction(transaction) const signature = await connection.sendTransaction(signedTransaction) await connection.confirmTransaction(signature, 'confirmed') console.log('Delegation signature:', signature) return signature } signAndSubmitDelegation() ``` Run it: ```bash node solana-delegate.js ``` ### Create the payout Once the delegation transaction confirms, create the payout the same way as EVM, at `/payouts/evm`. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "quote_id": "qu_000000000000", "sender_wallet_address": "YOUR_SOLANA_WALLET_ADDRESS" }' ``` Repeat the delegation (prepare, sign, submit) for every Solana payout. Each delegation only authorizes the amount tied to that specific quote. ## Related * [Payouts](/docs/payouts): status lifecycle, cover\_fees, testing sentinels, and webhooks * [Payout quotes](/docs/payout-quotes): full request and response fields * [Mint USDB](/docs/mint-usdb): mint to any Solana Devnet address via REST * [Payout with managed wallet](/docs/payout-managed-wallet): the no-signing alternative --- --- url: /docs/offramp-wallets.md description: >- What a BlindPay offramp wallet is, the chains and stablecoins it supports, minimums, and how deposits automatically convert to a fiat payout. --- An offramp wallet is a blockchain wallet that BlindPay creates and manages for you, tied to one of your customer's bank accounts. Every USDC or USDT deposit it receives is automatically converted to fiat and paid out to that bank account, with no quote or execute call on your side. It is the stablecoin-side mirror of a [virtual account](/docs/virtual-accounts): where a virtual account turns incoming fiat into stablecoin, an offramp wallet turns incoming stablecoin into fiat. ## How it works BlindPay always creates a [payout](/docs/payouts) automatically whenever the wallet receives a USDC or USDT transaction. You share the wallet's deposit address with whoever is paying your customer; the moment funds land, conversion and settlement to the linked bank account happen on their own. ### Supported chains and stablecoins | Chain | Stablecoins | Minimum | Additional fee | | --- | --- | --- | --- | | Tron | USDT only | 200 USDT | 15 USDT | | Solana | USDC only | 50 USDC | 0 USDC | | Ethereum | USDC only | No fixed minimum\* | 1 USDC | | Polygon | USDC and USDT | No fixed minimum\* | 0 USDC | | Arbitrum | USDC only | No fixed minimum\* | 0 USDC | | Base | USDC only | No fixed minimum\* | 0 USDC | \*The deposit only needs to cover its fees (additional fee, percentage fee, and bank transfer fee). A deposit smaller than the total fees is not converted. ### Fee example Sending 100 USDT to an offramp wallet on Tron that settles over ACH (assuming 1 USDT = $1.00): 1. Convert: 100 USDT = $100.00 2. Additional fee: 15 USDT = $15.00 3. Percentage fee: 0.1% of $100.00 = $0.10 4. Bank transfer fee: $0.40 (ACH) Final: $100.00 - $15.00 - $0.10 - $0.40 = **$84.50** delivered to the recipient's bank account. The additional fee is fixed per chain (see the table above); the percentage fee and bank transfer fee depend on your pricing and the payout rail. ## Create an offramp wallet An offramp wallet is created on an existing bank account, which becomes its payout destination. Once created, it has a deposit address on the network you chose; anything sent to that address is converted and paid out automatically. ### Prerequisites You must also [create a customer](/docs/learn/customers) and add a [bank account](/docs/bank-accounts) before creating an offramp wallet. The wallet inherits that bank account as its payout destination. ### The request ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/bank-accounts/ba_000000000000/offramp-wallets \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "network": "tron" }' ``` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `network` | enum | Yes | Production: `tron`, `solana`, `ethereum`, `polygon`, `arbitrum`, `base`. Development instances use the matching testnets: `solana_devnet`, `sepolia`, `polygon_amoy`, `arbitrum_sepolia`, `base_sepolia`. | | `external_id` | string | No | Your own reference for the wallet, echoed back on the response. | The response includes `id` (`ow_...`), `network`, `address`, and your `external_id`. Share `address` with whoever is paying your customer: any USDC or USDT sent to it on the chosen network is converted and paid out to the linked bank account automatically. ## Related * [Payouts](/docs/payouts): the payout created automatically on each deposit * [Bank accounts](/docs/bank-accounts): the settlement destination * [Virtual accounts](/docs/virtual-accounts): the fiat-side mirror --- --- url: /docs/blockchain-wallets.md description: >- Register an external, customer-controlled wallet address to receive stablecoin payins and send stablecoin payouts. --- A blockchain wallet (`bw_...`) is an externally owned wallet that your customer controls, not BlindPay. You register its address so BlindPay can deliver stablecoins to it or read a balance from it, but BlindPay never holds the private keys and cannot move funds out of it without the customer's signature or on-chain authorization. For a BlindPay-custodied alternative where you do not need the customer to sign anything, see [Managed wallet](/docs/wallets). Non-custodial by design: BlindPay cannot access, freeze, or recover funds in a blockchain wallet. If a payin or payout is misdirected because of a wrong address, BlindPay cannot reverse the on-chain transfer. ## What it's used for A blockchain wallet is the endpoint on both sides of a stablecoin movement: * **Payin delivery target.** On a [payin quote](/docs/payin-quotes), set `blockchain_wallet_id` to a `bw_...` ID and the stablecoin lands in that wallet once the payin completes. * **Payout source.** On a [payout](/docs/payouts), the customer authorizes the transfer out of their own blockchain wallet: an on-chain [`approve` on EVM](/docs/payout-evm), a [signed Stellar transaction](/docs/payout-stellar), or a [Solana delegation](/docs/payout-solana), depending on the network. ## Supported networks | Network | Type | Notes | | --- | --- | --- | | `ethereum` | EVM, production | | | `polygon` | EVM, production | | | `base` | EVM, production | | | `arbitrum` | EVM, production | | | `stellar` | Non-EVM, production | | | `solana` | Non-EVM, production | | | `tron` | Non-EVM, production | Beta, requires the `otc` subscription feature on the instance | | `sepolia` | EVM, development | Ethereum testnet | | `polygon_amoy` | EVM, development | Polygon testnet | | `base_sepolia` | EVM, development | Base testnet | | `arbitrum_sepolia` | EVM, development | Arbitrum testnet | | `stellar_testnet` | Non-EVM, development | | | `solana_devnet` | Non-EVM, development | | Development instances only accept the testnet networks; production instances only accept the mainnet networks. See [Supported chains](/docs/kb/supported-chains) for the full chain and token matrix. ## Prerequisites A customer must exist before you add a blockchain wallet for them. ## Add a blockchain wallet There are two ways to register a wallet address, controlled by the `is_account_abstraction` field: * **Signed message (`is_account_abstraction: false`)**, EVM networks only. The customer signs a message with their wallet, and BlindPay recovers the address from the signature server-side, so you never send an address BlindPay has to trust blindly. * **Direct address (`is_account_abstraction: true`)**, any supported network. You submit the address directly. Despite the field name, this is also how you register Stellar, Solana, and Tron addresses, and how you register EVM smart-contract wallets that cannot produce the standard signature flow. Double-check the address before submitting it, especially with the direct-address method. BlindPay cannot verify that an address you paste in actually belongs to your customer, and a stablecoin delivered to the wrong address cannot be recovered. ### Signed message flow The steps are: ### Get the message to sign ```bash [cURL] curl https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/blockchain-wallets/sign-message \ --header 'Authorization: Bearer YOUR_API_KEY' ``` This returns a fixed message string for the customer to sign. It does not change between requests. ### Sign the message Use a library like wagmi or ethers.js to sign the message with the customer's wallet and get the signature transaction hash. ```js [ethers.js] import { signMessage } from '@wagmi/core' // setup your wagmiConfig const message = '' const signature_tx_hash = await signMessage(wagmiConfig, { message, }) ``` ### Add the blockchain wallet Submit the signature. BlindPay recovers the address and stores it, so `address` is omitted from the request body. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/blockchain-wallets \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "name": "John personal wallet", "network": "polygon", "is_account_abstraction": false, "signature_tx_hash": "0x..." }' ``` ### Direct address flow Set `is_account_abstraction: true` and pass the `address` field directly. This is the only option for `stellar`, `solana`, and `tron`, and it also covers EVM smart-contract wallets. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/customers/re_000000000000/blockchain-wallets \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "name": "John personal wallet", "network": "polygon", "is_account_abstraction": true, "address": "0x..." }' ``` Each network validates the address format server-side (for example Stellar addresses must start with `G`, Solana addresses are base58, Tron addresses start with `T`), but this is a format check, not proof of ownership. It cannot substitute for the signed-message flow's guarantee. ## Response fields | Field | Description | | --- | --- | | `id` | `bw_...` | | `name` | The label you supplied | | `network` | One of the supported networks above | | `is_account_abstraction` | Whether the wallet was registered by direct address (`true`) or signed message (`false`) | | `address` | The wallet address, lower-cased for EVM networks | ## Webhooks | Event | Fires when | | --- | --- | | `blockchainWallet.new` | A blockchain wallet is successfully created | See [Webhooks](/docs/learn/webhooks) for delivery and signature verification. ## Related * [Managed wallet](/docs/wallets): a BlindPay-custodied alternative that needs no customer signature * [Payin with blockchain wallet](/docs/payin-blockchain-wallet): use a blockchain wallet as the payin delivery target * [Payouts](/docs/payouts): authorize a payout from a blockchain wallet, per network * [Supported chains](/docs/kb/supported-chains): full chain and token matrix * [Webhooks](/docs/learn/webhooks): event delivery and signature verification --- --- url: /docs/payin-managed-wallet.md description: >- Accept a fiat payment and have the equivalent stablecoins delivered into a BlindPay-managed wallet, two REST calls with no signing. --- This is the simplest way to receive a [payin](/docs/payins): the sender pays fiat, and BlindPay delivers the equivalent stablecoins into a [managed wallet](/docs/wallets). BlindPay generates the wallet address and custodies the balance, so there is no external wallet to connect and nothing to sign. You create a payin quote and create the payin, two REST calls. If the stablecoins should be delivered to a wallet the customer controls instead, see [Payin with blockchain wallet](/docs/payin-blockchain-wallet). ## Prerequisites You also need: * A [customer](/docs/learn/customers) with `kyc_status: "approved"` * A [managed wallet](/docs/wallets) (`bl_...`) for that customer as the delivery target ### Create a payin quote A payin quote locks in how much fiat the sender sends and how much the wallet receives. Pass `wallet_id` to target the managed wallet; BlindPay detects the delivery network from the wallet, so you never pass a network on a payin quote. This example quotes an ACH payin with the sender covering the fee, so `request_amount` is the amount the sender sends. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "wallet_id": "bl_000000000000", "currency_type": "sender", "cover_fees": true, "request_amount": 10000, "payment_method": "ach", "token": "USDB" }' ``` `request_amount` is an integer in minor units, so `10000` here means $100.00. On development instances the token is always `USDB`; in production use `USDC` or `USDT`. Save the quote ID (`pq_...`); you have 5 minutes to create the payin before it expires. ### Create the payin Create the payin from the quote ID. This generates the payment instructions the sender pays into. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payins/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "payin_quote_id": "pq_000000000000" }' ``` The response carries the instructions matching the quote's `payment_method`: `memo_code` and `blindpay_bank_details` for ACH and wire, `pix_code` for Pix, `clabe` for SPEI. Share them with the sender. ## What happens next Once the fiat deposit clears, BlindPay converts it and credits the managed wallet. Two webhooks confirm it: `payin.complete` for the payin itself and `wallet.inbound` when the stablecoins land in the wallet. On a development instance the payin settles automatically about 30 seconds after creation. ## Related * [Payins](/docs/payins): the payin reference, statuses, testing sentinels, and webhooks * [Payin quotes](/docs/payin-quotes): full request and response fields * [Managed wallets](/docs/wallets): the delivery wallet, including the balance endpoint * [Payin with blockchain wallet](/docs/payin-blockchain-wallet): deliver to a customer-controlled wallet instead --- --- url: /docs/payin-blockchain-wallet.md description: >- Accept a fiat payment and have the equivalent stablecoins delivered to a wallet your customer controls. --- This tutorial receives a [payin](/docs/payins) into a [blockchain wallet](/docs/blockchain-wallets): a self-custodied wallet your customer controls. The flow is the same two REST calls as the managed-wallet path; the difference is that you register the customer's wallet address first, and the stablecoins are delivered on-chain to an address BlindPay does not custody. If you'd rather hold the balance inside BlindPay, see [Payin with managed wallet](/docs/payin-managed-wallet). ## Prerequisites You also need: * A [customer](/docs/learn/customers) with `kyc_status: "approved"` * A registered [blockchain wallet](/docs/blockchain-wallets) (`bw_...`) for that customer, added either by signed message or by direct address BlindPay cannot recover stablecoins delivered to a wrong address. Double-check the wallet's `address` and `network` when registering it. ### Create a payin quote Pass `blockchain_wallet_id` to target the customer's wallet; BlindPay detects the delivery network from the wallet record, so you never pass a network on a payin quote. This example quotes an ACH payin with the sender covering the fee, so `request_amount` is the amount the sender sends. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payin-quotes \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "blockchain_wallet_id": "bw_000000000000", "currency_type": "sender", "cover_fees": true, "request_amount": 10000, "payment_method": "ach", "token": "USDB" }' ``` `request_amount` is an integer in minor units, so `10000` here means $100.00. On development instances the token is always `USDB`; in production use `USDC` or `USDT`. Save the quote ID (`pq_...`); you have 5 minutes to create the payin before it expires. ### Create the payin Create the payin from the quote ID. This generates the payment instructions the sender pays into. ```bash [cURL] curl --request POST \ --url https://api.blindpay.com/v1/instances/in_000000000000/payins/evm \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "payin_quote_id": "pq_000000000000" }' ``` The response carries the instructions matching the quote's `payment_method`: `memo_code` and `blindpay_bank_details` for ACH and wire, `pix_code` for Pix, `clabe` for SPEI. Share them with the sender. ## What happens next Once the fiat deposit clears, BlindPay converts it and sends the stablecoins on-chain to the registered wallet address, then fires `payin.complete`. Because the wallet is customer-controlled, there is no `wallet.inbound` event; track delivery via the payin's `tracking_complete.transaction_hash` or on a block explorer. On a development instance the payin settles automatically about 30 seconds after creation. ## Related * [Payins](/docs/payins): the payin reference, statuses, testing sentinels, and webhooks * [Payin quotes](/docs/payin-quotes): full request and response fields * [Blockchain wallets](/docs/blockchain-wallets): the signed-message and direct-address registration flows * [Payin with managed wallet](/docs/payin-managed-wallet): hold the delivered balance inside BlindPay instead --- --- url: /docs/payable-managed-wallet.md description: >- Register a bill your customer owes, then pay it from a BlindPay-custodied wallet, with no on-chain approval and no wallet prompt. --- This tutorial registers a [payable](/docs/payables) and pays it from a [managed wallet](/docs/learn/customers), a wallet BlindPay custodies on the customer's behalf. Because BlindPay controls the wallet, there is no `approve` call and no signature to collect: register the bill, quote it, execute the payout, done. If the funds live in a wallet your user controls instead, you need the approval step: see [Payable with EVM](/docs/payable-evm). The examples below use `base_sepolia` and `USDB` since they're the development network and test token. Swap in the matching production network and token (`USDC` or `USDT`) when you go live. This is the shape most "the platform holds the stablecoins" products want: your user pastes a bill and it gets paid, with no wallet interaction at any point. This path is API only. The BlindPay dashboard pays payables from a connected wallet, so use the API when the funds are in a managed wallet. ## Prerequisites You also need: 1. A [customer](/docs/learn/customers) with `kyc_status: "approved"` 2. A managed wallet for that customer, funded with enough stablecoin to cover the bill 3. A real boleto or PIX copia e cola code to pay ### Register the payable ```js [index.js] const payableResponse = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payables', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ customer_id: 're_000000000000', currency: 'BRL', boleto_barcode: '34191790010104351004791020150008191070026000', }), } ) const payable = await payableResponse.json() ``` The payable is created in `draft`. Registering the same code twice while a live payable exists fails with `duplicate_payable`; to retry a failed attempt, quote the same payable again (it returns to `draft`), and a deleted draft frees the code for re-registration. ### Quote against the managed wallet Pass `network` and `token` matching the managed wallet, and `payable_id` instead of `bank_account_id`. Do not send `request_amount` or `currency_type`: the amount comes from the payable, re-resolved from the rail (fines, interest) for a boleto. ```js [index.js] const quoteResponse = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/quotes', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ payable_id: payable.id, network: 'base_sepolia', token: 'USDB', }), } ) const quote = await quoteResponse.json() // Resolved from the rail, not from your user's expectations. console.log(quote.receiver_amount, quote.sender_amount) ``` The response still includes a `contract` object. Ignore it here: it exists for external wallets, and a managed wallet needs no approval. ### Execute the payout `sender_wallet_address` is the managed wallet's own address. BlindPay recognises it as custodied and moves the funds internally rather than running an ERC-20 `transferFrom`. ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ quote_id: quote.id, sender_wallet_address: 'THE_MANAGED_WALLET_ADDRESS', }), } ) const payout = await response.json() ``` The payout starts `processing`, with no signature collected from anyone, and the payable's own `status` now mirrors it. An insufficiently funded managed wallet fails during collection, not at quote time, so the payout is created and then fails. Check the wallet's balance against `sender_amount` before executing if you want to catch that earlier. ### Track it to settlement A PIX code usually completes in under a minute. A boleto settles on a banking day. Subscribe to `payable.complete` to hear when the bill is paid and `payable.update` for changes while it is in flight. ```js [index.js] const detail = await fetch( `https://api.blindpay.com/v1/instances/in_000000000000/payables/${payable.id}`, { headers: { Authorization: 'Bearer YOUR_API_KEY' } } ).then(r => r.json()) console.log(detail.status, detail.amount) ``` ## Related * [Payables](/docs/payables): registration, amount semantics, lifecycle, and webhooks * [Payable with EVM](/docs/payable-evm): the same flow from a wallet your user controls --- --- url: /docs/payable-evm.md description: >- Register a bill your customer owes, then pay it from an external EVM wallet by quoting it, approving the ERC-20 pull, and executing the payout. --- This tutorial registers a [payable](/docs/payables) and pays it from an external EVM wallet on Ethereum, Base, Polygon, or Arbitrum. All stablecoins on EVM chains are ERC-20 tokens, so authorization means calling `approve` on the token contract to let BlindPay pull the quoted amount. If the funds live in a BlindPay-custodied wallet instead, skip the approval entirely: see [Payable with managed wallet](/docs/payable-managed-wallet). The examples below use `base_sepolia` and `USDB` since they're the development network and test token. Swap in the matching production network and token (`USDC` or `USDT`) when you go live. Payables are **EVM only** today, and only boleto and PIX payables can be paid; a non-EVM network is refused at quote time with `payable_network_not_supported`. ## Prerequisites You also need: 1. A [customer](/docs/learn/customers) with `kyc_status: "approved"`. There is no bank account to register: the bill names its own beneficiary 2. A real boleto linha digitável or PIX copia e cola code to pay 3. [An RPC provider URL for Base Sepolia](https://chainlist.org/?search=base\&testnets=true) 4. [A wallet funded with testnet ether](https://www.alchemy.com/faucets/base-sepolia) to pay gas 5. [The private key, or any way to instantiate the wallet with ethers.js](https://docs.ethers.org/v6/api/wallet/#BaseWallet_new) ### Register the payable ```js [index.js] const payableResponse = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payables', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ customer_id: 're_000000000000', currency: 'BRL', boleto_barcode: '34191790010104351004791020150008191070026000', }), } ) const payable = await payableResponse.json() ``` The payable is created in `draft`. Registering the same code twice while a live payable exists fails with `duplicate_payable`; to retry a failed attempt, quote the same payable again (it returns to `draft`), and a deleted draft frees the code for re-registration. ### Quote the payable and approve the tokens Create a quote with `payable_id` instead of `bank_account_id`. Do not send `request_amount` or `currency_type`: the amount comes from the payable, re-resolved from the rail (fines, interest) for a boleto. The `contract` object in the response carries everything the `approve` call needs, including `amount` already adjusted for the token's decimals. ```js [index.js] import { ethers } from 'ethers' const quoteResponse = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/quotes', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ payable_id: payable.id, network: 'base_sepolia', token: 'USDB', }), } ) const quote = await quoteResponse.json() // What the bill actually costs, resolved at quote time. Show this before // asking anyone to sign anything. console.log(quote.receiver_amount, quote.sender_amount) const provider = new ethers.JsonRpcProvider('YOUR_RPC_URL') const wallet = new ethers.Wallet('YOUR_PRIVATE_KEY', provider) const token = new ethers.Contract( quote.contract.address, quote.contract.abi, wallet ) const approval = await token.approve( quote.contract.blindpayContractAddress, quote.contract.amount ) await approval.wait() ``` The quote expires 5 minutes after creation. If the approval transaction is slow to mine, quote again rather than committing an expired one. ### Execute the payout Executing the payout is what actually pays the bill. `sender_wallet_address` is the wallet that just approved. ```js [index.js] const response = await fetch( 'https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ quote_id: quote.id, sender_wallet_address: wallet.address, }), } ) const payout = await response.json() ``` The payout starts `processing`, and the payable's own `status` now mirrors it. If the wallet has not approved enough, this call fails immediately with `erc20_allowance_insufficient` rather than returning a payout that dies minutes later. Approve at least `contract.amount` and retry. ### Track it to settlement Stablecoins are collected first, then the bill is paid. A PIX code usually completes in under a minute; a boleto settles on a banking day. Subscribe to `payable.complete` to hear when the bill is paid and `payable.update` for bill-level changes (a payout claimed it, the attempt failed or was refunded and it is payable again, or a draft was deleted). The executing payout emits its own `payout.*` events for the mid-flight detail. See [Payables](/docs/payables#webhooks) for details. ```js [index.js] const detail = await fetch( `https://api.blindpay.com/v1/instances/in_000000000000/payables/${payable.id}`, { headers: { Authorization: 'Bearer YOUR_API_KEY' } } ).then(r => r.json()) console.log(detail.status, detail.amount) ``` ## Related * [Payables](/docs/payables): registration, amount semantics, lifecycle, and webhooks * [Payable with managed wallet](/docs/payable-managed-wallet): the same flow with no approval step --- --- url: /docs/kb.md description: >- Reference guides for supported chains, countries, payment methods, compliance requirements, and processing timelines. --- Reference guides for topics that span the API: chains and tokens, compliance, and processing timelines. ## Guides Operational how-tos for running payments day to day. | Guide | What it covers | | --- | --- | | [Payout descriptor](/docs/kb/payout-descriptor) | How the sender's name appears on a recipient's bank statement, per rail | | [On-hold transactions](/docs/kb/on-hold-transactions) | Transactions held for compliance review and how they resolve | | [Nested payments](/docs/kb/nested-payments) | Recognizing payments made on behalf of unseen parties, and staying compliant | | [POBO and COBO](/docs/kb/pobo-cobo) | Paying and collecting for your customers, and what puts their name on a Wire | | [SWIFT deliverability](/docs/kb/swift-deliverability) | Documents and address formatting that maximize SWIFT delivery success | | [SWIFT statuses](/docs/kb/swift-statuses) | Tracking SWIFT compliance documents through review and approval | ## Coverage Where BlindPay operates and what it supports. | Guide | What it covers | | --- | --- | | [Supported countries](/docs/kb/supported-countries) | Every country by tier: standard, high-risk, and prohibited | | [Supported chains](/docs/kb/supported-chains) | Chain and token matrix across payins, payouts, wallets, and transfers | | [Payment methods](/docs/kb/payment-methods) | Bank rails by country and currency, for payins and payouts | | [Smart contracts](/docs/kb/smart-contracts) | USDB test stablecoin and its contract addresses across supported networks | | [Cut-off times](/docs/kb/cut-off-times) | Processing windows for ACH, wire, SWIFT, Pix, SPEI, and more | ## Verification Everything customers need to pass KYC and KYB. | Guide | What it covers | | --- | --- | | [KYC basics](/docs/kb/kyc-basics) | Document-quality standards and submission guidelines for individuals | | [KYC requirements](/docs/kb/kyc) | Verification levels, required fields, limits, and the terms of service flow | | [KYB documents](/docs/kb/kyb-documents) | Business verification documents: formation, ownership, UBOs, and address | | [Proof of address](/docs/kb/proof-of-address) | Accepted proof-of-address documents for businesses and individuals | | [Source of funds](/docs/kb/source-of-funds) | Documentation to verify source of funds and source of wealth | | [NAICS codes](/docs/kb/naics-codes) | Find the NAICS industry code for your business during onboarding | | [Virtual accounts](/docs/kb/virtual-accounts) | Documentation for the virtual account evaluation, beyond standard KYB | ## Compliance Reviews, requests for information, and what gets blocked. | Guide | What it covers | | --- | --- | | [Prohibited activities](/docs/kb/prohibited-activities) | High-risk and prohibited business activities, and disclosure obligations | | [Information requests](/docs/kb/information-requests) | How RFIs work when compliance needs missing KYC or KYB details | | [Instance requests](/docs/kb/instance-requests) | RFIs about your own account and how to respond before the deadline | | [Rejection reasons](/docs/kb/rejection-reasons) | Reason codes returned when an application or document is rejected | --- --- url: /docs/learn.md description: >- Guides covering key BlindPay concepts: instances, API keys, webhooks, and billing. --- # Learn Concepts that apply to every integration, regardless of whether you build on the fiat or stablecoin flavor of the API. ## In this section | Guide | What it covers | | --- | --- | | [Instances](/docs/learn/instances) | Development vs. production environments, and how to create one | | [Sandbox vs. production](/docs/learn/sandbox-vs-production) | Behavioral differences, test tokens, and simulating failures | | [Billing](/docs/learn/billing) | Fee structure and invoices | | [Partner fees](/docs/learn/partner-fees) | Add markup to transactions and withdraw monthly revenue | | [API keys](/docs/learn/api-keys) | Creating keys, authentication, and security | | [Webhooks](/docs/learn/webhooks) | All events, setup, and verification |