US Remittance Integration Guide
How do I send cross-border remittances from US customers using Cybrid-integrated payouts?
US Remittance lets US individuals send cross-border remittances using Cybrid-integrated payouts. Your customer
funds a US dollar balance from their own bank account, Cybrid converts it to a stablecoin, and a remittance plan
delivers the funds to a recipient abroad in the local currency — all through a single API integration.
Under the hood, the customer funds a USD fiat account (a US bank linked via Plaid, funded by an ACH pull),
converts USD to USDC_SOL in a trading account, then a remittance plan pays out to the recipient's foreign bank
account over the local rail. Senders are onboarded as individuals with KYC, and recipients are tracked as
verified counterparties.
India (INR over IFSC) is the example corridor used throughout. Other corridors are available and follow the
same flow with corridor-specific fields.
At a high level, you will:
- Onboard and verify the customer with KYC.
- Create the USD fiat account, the USDC_SOL trading account, and the customer's deposit bank account.
- Link the customer's US bank account and fund the USD account by ACH pull.
- Convert USD to USDC_SOL.
- Create and verify the recipient counterparty and their foreign bank account.
- Create and execute the remittance plan to pay out.
Flow of funds
Figure: US remittance flow of funds.
Funds move left to right across the Cybrid boundary. The customer's external US bank account (linked via Plaid)
is pulled over ACH into their USD fiat account inside Cybrid, where KYC and the deposit bank account are held. A
trade settles the USD into a USDC_SOL trading account, and a remittance plan converts and pays out to the
recipient's foreign external bank account. The recipient is tracked as a verified individual counterparty.
Arrow colors in the diagram: green is fiat movement, orange is crypto movement, blue is trade settlement, and
tan is a book transfer.
Table of Contents
- Prerequisites
- Customer onboarding
- Create accounts
- Create the deposit bank account
- Link the bank account
- Fund the USD account
- Convert USD to USDC_SOL
- Create the recipient counterparty
- Create the recipient external bank account
- Create and execute the remittance plan
- Webhooks
- Additional resources
1. Prerequisites
Before integrating the US Remittance flow, configure your sandbox bank with the required features.
Bank configuration
When creating your bank in the sandbox environment, configure these settings:
Customer type support:
Enable individual customers:
- Individual customers (
individual_customers)
Fiat account assets:
Enable the fiat currency for the funding country:
- USD
Trading symbols:
Enable the USDC_SOL-USD trading pair for the USD to USDC_SOL conversion.
Enable the payout symbols for your corridorsContact Cybrid support to enable the payout symbols for the payout corridors you require (for
example,INR-USDC_SOL). Confirm corridor support viasupported_payout_symbols.
Supported corridors for C2C remittancesThis guide implements a consumer-to-consumer (C2C) remittance. For the full list of destination
corridors and the participant types each supports, see
Supported Corridors.
Register your Plaid redirect URIRegister your redirect URI with Cybrid support before initiating Plaid workflows. Contact support
with your sandbox and production redirect URIs. HTTP redirect URIs work in sandbox — for local
development usehttp://localhost:4200/bank-account-connect, which is preconfigured. Production
requires HTTPS.
2. Customer onboarding
Create the customer, then verify their identity with KYC through the Persona SDK.
Create the customer
Endpoint: POST /api/customers
Reference: Create Customer
Set type to individual. Only type is required at creation.
POST /api/customers
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "individual",
"email_address": "[email protected]",
"phone_number": "555-0100"
}{
"guid": "customer_guid",
"type": "individual",
"state": "storing",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}
PII fields are optional at creationNot all fields are required to create a customer. Email and phone help identify customers beyond
thecustomer_guid. Do not provide the customer's name at creation — Persona collects it during
KYC, and the legal name returned by Persona becomes the customer's name on file. This
Persona-captured name is also used downstream for bank account ownership verification.
Customer states
| State | Description |
|---|---|
storing | Platform is storing customer details |
unverified | Customer created, awaiting identity verification |
verified | Customer identity verified |
rejected | Customer verification rejected |
frozen | Customer account frozen |
Poll GET /api/customers/{customer_guid} until state transitions to unverified.
Polling intervalUse exponential backoff starting at 1 second. In sandbox, state transitions typically complete
within seconds. In production, some operations may take longer.
Verify the customer
Endpoint: POST /api/identity_verifications
Reference: Create Identity Verification
Create the Persona inquiry. Cybrid returns a persona_inquiry_id you pass to the Persona SDK to launch the
capture flow — government ID and selfie (KYC). Use method: id_and_selfie:
POST /api/identity_verifications
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "kyc",
"method": "id_and_selfie",
"customer_guid": "customer_guid"
}{
"guid": "identity_verification_guid",
"type": "kyc",
"method": "id_and_selfie",
"customer_guid": "customer_guid",
"state": "storing",
"outcome": null,
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}
Preparing customers before verificationPreparing customers before verification greatly increases success rates.
See: Verify an Individual Customer
Customer must be unverifiedIdentity verification can only be initiated for customers in the
unverifiedstate. The customer
state transitions fromstoringtounverifiedbefore you can start the verification process.
Identity verification states
| State | Description |
|---|---|
storing | Platform is storing verification details |
waiting | Waiting for the customer to complete verification |
pending | Verification submitted, awaiting processing |
reviewing | Verification under manual review |
expired | Verification session expired |
completed | Verification completed (check the outcome field) |
Poll GET /api/identity_verifications/{identity_verification_guid} until state transitions to completed,
then confirm outcome is passed.
Integrate the Persona SDK
Persona offers Hosted Flow, Embedded Flow, and native iOS/Android SDKs. The example below uses the Embedded Flow via NPM — see Persona's integration methods to pick the option that fits your app.
Recommended integrationCybrid recommends the embedded flow with the native iOS and Android SDKs for the best customer experience.
Install the SDK:
npm install [email protected] --saveInitialize the client with the persona_inquiry_id from the identity verification response:
import Persona from 'persona';
const client = new Persona.Client({
inquiryId: 'persona_inquiry_id',
language: 'en',
onReady: () => client.open(),
onComplete: ({ inquiryId, status, fields }) => {
// Poll Cybrid or subscribe to identity_verification.completed
},
onCancel: ({ inquiryId, sessionToken }) => { /* user dismissed */ },
onError: (error) => { /* log SDK error */ }
});Key callbacks:
onReady— SDK ready to open.onComplete— user finished the flow; verification can still fail downstream, confirm via the Cybrid API.onCancel— user dismissed the flow.onError— SDK error.
The language parameter accepts ISO 639-1 codes. See
Persona's supported languages for the full list.
Resume incomplete sessionsIf the customer abandons the flow before completing, generate a new session token via
POST /api/persona_sessionsand pass it to the SDK assessionTokeninstead ofinquiryId.
See Resume Persona Inquiry Session.
Sandbox testing withexpected_behavioursIn sandbox, set
expected_behaviourson the identity verification request to force a deterministic
outcome — useful for end-to-end tests without driving the Persona flow.
Value Effect passed_immediatelyVerification completes with outcome: passed.failed_immediatelyVerification completes with outcome: failed.tax_id_not_checkedTax ID check is skipped during verification.
Example expected_behaviours request body:
{
"type": "kyc",
"method": "id_and_selfie",
"customer_guid": "customer_guid",
"expected_behaviours": ["passed_immediately"]
}
Sandbox onlyThe
expected_behavioursfield is rejected in production with a 400 error:
Parameter expected_behaviours not permitted in production. Remove the field before deploying.
Sandbox identity matching for account-ownership verificationAccount-ownership verification compares the customer's Persona-captured name to the
bank account holder name returned by Plaid. In sandbox, both sides have a fixed
default identity. A mismatch prevents auto-approval and falls back to Persona
document submission.Persona's default sandbox identity (what Persona returns if no other name is entered):
Field Value First name ALEXANDER JLast name SAMPLEAddress 600 California Street, San Francisco, CA, 94109DOB 1977-07-17Plaid's default sandbox identity (returned by Plaid for account-ownership):
first nameAlberta, last nameCharleson. See
Plaid testing identity.To auto-approve
type: bank_account,method: account_ownershipin sandbox, either:
- Complete the Persona step using
Alberta/Charlesonso the Persona-captured name
matches Plaid's sandbox identity, or- Include
expected_behaviours: ["passed_immediately"]on the bank account identity
verification to bypass the check entirely.In production, the Persona-captured name is reused for bank account ownership
verification. A mismatch with the Plaid-returned bank account holder name returns
failure_code: name_check_failure. See
Verify External Bank Accounts.
Related: Create a Customer · Verify an Individual Customer · Verify External Bank Accounts
CheckpointThe customer is
verified. You can now create their accounts and move on to funding.
3. Create accounts
Create the USD fiat account and the USDC_SOL trading account for the customer.
Endpoint: POST /api/accounts
Reference: Create Account
Create the USD fiat account
POST /api/accounts
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "fiat",
"customer_guid": "customer_guid",
"asset": "USD",
"name": "USD Account"
}{
"guid": "fiat_account_guid",
"type": "fiat",
"customer_guid": "customer_guid",
"asset": "USD",
"name": "USD Account",
"state": "storing",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Create the USDC_SOL trading account
POST /api/accounts
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "trading",
"customer_guid": "customer_guid",
"asset": "USDC_SOL",
"name": "USDC_SOL Trading Account"
}{
"guid": "trading_account_usdc_sol_guid",
"type": "trading",
"customer_guid": "customer_guid",
"asset": "USDC_SOL",
"name": "USDC_SOL Trading Account",
"state": "storing",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Account states
| State | Description |
|---|---|
storing | Platform is storing account details |
created | Account created and ready for use |
Poll GET /api/accounts/{account_guid} until state transitions to created.
Account typesFiat accounts hold currencies like USD, while trading accounts hold cryptocurrencies like
USDC_SOL. The USDC_SOL trading account is the source account for the remittance plan in
step 10.
Customer fiat accounts require a deposit bank accountEvery customer fiat account requires a
maindeposit bank account before any funding
transfer can complete. Create it immediately after the fiat account — see the next step and
Customer Deposit Bank Accounts.
Related: Creating Platform Accounts
4. Create the deposit bank account
Create a main deposit bank account for the customer's USD fiat account. This is required before any funding
transfer can complete.
Endpoint: POST /api/deposit_bank_accounts
Reference: Create Deposit Bank Account
POST /api/deposit_bank_accounts
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "main",
"account_guid": "fiat_account_guid",
"customer_guid": "customer_guid"
}{
"guid": "deposit_bank_account_guid",
"type": "main",
"customer_guid": "customer_guid",
"account_guid": "fiat_account_guid",
"asset": "USD",
"state": "storing",
"unique_memo_id": "unique_memo_id",
"counterparty_name": "John Doe",
"account_details": [
{ "account_number": "account_number" }
],
"routing_details": [
{ "routing_number_type": "ABA", "routing_number": "routing_number" }
],
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Deposit bank account states
| State | Description |
|---|---|
storing | Platform is storing account details |
created | Deposit bank account created |
Poll GET /api/deposit_bank_accounts/{deposit_bank_account_guid} until state transitions to created.
Deposit bank account enforcement (effective 2026-06-17)From 2026-06-17, every customer fiat account must have a
maindeposit bank account before any
funding transfer (deposit or withdrawal) can be created. Provision the deposit bank account
immediately after creating the customer's fiat account — see
Customer Deposit Bank Accounts.
Routing detailsThe response includes
account_details(account number),routing_details(ABA routing number),
counterparty_name(account holder name), andcounterparty_address(account holder address)
for incoming wires.
No deposit bank account errorFunding transfers against a customer fiat account that has no
maindeposit bank account fail
with HTTP 422 and message_codeno_deposit_bank_account. Remediate by creating themain
deposit bank account on the fiat account, then retry the transfer.
Related: Customer Deposit Bank Accounts
5. Link the bank account
Connect the customer's US bank account via Plaid, then create the external bank account for ACH funding.
Create the Plaid workflow
Endpoint: POST /api/workflows
Reference: Create Workflow
POST /api/workflows
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "plaid",
"kind": "link_token_create",
"customer_guid": "customer_guid",
"language": "en",
"link_customization_name": "default",
"redirect_uri": "http://localhost:4200/bank-account-connect"
}{
"guid": "workflow_guid",
"type": "plaid",
"customer_guid": "customer_guid",
"state": "storing",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Workflow states
| State | Description |
|---|---|
storing | Platform is storing workflow details |
completed | Workflow completed, link token available |
failed | Workflow failed |
Poll GET /api/workflows/{workflow_guid} until state transitions to completed, then retrieve the Plaid
link token from the workflow.
Initialize the Plaid Link SDK
Use the generated plaid_link_token to initialize the Plaid Link SDK in your client
application. The handler below includes callbacks for success, exit, and other events. Use
the public_token and account id from the onSuccess callback to create the external
bank account.
Plaid Link integration methodsReview the official Plaid Link documentation for
up-to-date platform-specific details (Web, iOS, Android, or React Native).
const handler = Plaid.create({
token: 'GENERATED_LINK_TOKEN', // The plaid_link_token from the workflow
onSuccess: (public_token, metadata) => {
// A Plaid public_token and account metadata are returned
// Use these values to create the external bank account
// https://plaid.com/docs/link/web/#onsuccess
},
onLoad: () => {},
onExit: (err, metadata) => {
// Handle the case where the user exits the flow
// https://plaid.com/docs/link/web/#onexit
},
onEvent: (eventName, metadata) => {
// Optionally subscribe to events during the Plaid flow
// https://plaid.com/docs/link/web/#onevent
},
});
Raw routing details fallbackIf a domestic customer cannot link their bank account through Plaid (for example, their
institution is not supported by Plaid), contact Cybrid to create an external bank account using
raw routing details. The customer provides their bank's routing number, account number, and
account type to Cybrid, who provisions the account withaccount_kindraw_routing_details.
Create the external bank account
Endpoint: POST /api/external_bank_accounts
Reference: Create External Bank Account
POST /api/external_bank_accounts
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"name": "US Checking",
"account_kind": "plaid",
"customer_guid": "customer_guid",
"asset": "USD",
"plaid_public_token": "plaid_public_token",
"plaid_account_id": "plaid_account_id"
}{
"guid": "external_bank_account_guid",
"name": "US Checking",
"asset": "USD",
"account_kind": "plaid",
"customer_guid": "customer_guid",
"state": "storing",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}External bank account states
| State | Description |
|---|---|
storing | Platform is storing account details |
completed | Account linked and ready for use |
failed | Account linking failed |
refresh_required | Plaid connection needs refresh |
unverified | Account awaiting verification |
deleting | Account being removed (auto-managed; for example, account no longer valid) |
deleted | Account removed and must be re-added |
Poll GET /api/external_bank_accounts/{external_bank_account_guid} until state transitions to
unverified, then verify bank account ownership. The account transitions to completed once the
verification passes, at which point it is ready for funding.
Verify the external bank account
Endpoint: POST /api/identity_verifications
Reference: Create Identity Verification
Individual customers must verify ownership of the linked bank account before funding transfers can be
created. Because the account was linked with Plaid (account_kind: plaid), use type: bank_account and
method: account_ownership. The check confirms the Plaid-returned account holder name matches the
customer's KYC name.
Wait for the unverified stateCreate the bank account identity verification only after the external bank account reaches
unverified. Attempting verification while the account isstoringreturns a
422 Invalid bank account to verifyerror.
POST /api/identity_verifications
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "bank_account",
"method": "account_ownership",
"customer_guid": "customer_guid",
"external_bank_account_guid": "external_bank_account_guid"
}{
"guid": "identity_verification_guid",
"type": "bank_account",
"method": "account_ownership",
"customer_guid": "customer_guid",
"external_bank_account_guid": "external_bank_account_guid",
"state": "storing",
"outcome": null,
"failure_codes": [],
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Bank account verification states
| State | Description |
|---|---|
storing | Platform is storing verification details |
waiting | Waiting for a supplementary document (a Persona inquiry is created) |
pending | Verification submitted, awaiting processing |
reviewing | Verification under manual review |
expired | Verification session expired |
completed | Verification completed (check the outcome field) |
Poll GET /api/identity_verifications/{identity_verification_guid} until state transitions to
completed, then confirm outcome is passed. When the verification passes, the external bank account
transitions to completed and is ready for funding. On failed, inspect failure_codes (for example
name_check_failure) and create a new verification to retry.
Sandbox testingTo auto-approve in sandbox, either match the customer's Persona-verified name to Plaid's default
sandbox identity (Alberta Charleson), or includeexpected_behaviours: ["passed_immediately"]on
the verification request to bypass the check. Theexpected_behavioursfield is not permitted in
production.
Related: Verify External Bank Accounts · Plaid Integration · Add Customer External Bank Accounts · Refresh External Bank Accounts
6. Fund the USD account
Pull funds from the linked US bank account into the USD fiat account over ACH. Create a funding quote, then
create the funding transfer against it.
Create the funding quote
Endpoint: POST /api/quotes
Reference: Create Quote
POST /api/quotes
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"product_type": "funding",
"customer_guid": "customer_guid",
"asset": "USD",
"side": "deposit",
"deliver_amount": 10000
}{
"guid": "quote_guid",
"product_type": "funding",
"customer_guid": "customer_guid",
"asset": "USD",
"side": "deposit",
"deliver_amount": 10000,
"receive_amount": 10000,
"fee": 0,
"expires_at": "2026-07-19T12:00:30.000Z",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}
Amounts are in base unitsAmounts are in base units. For USD,
10000= $100.00 (cents). For USDC_SOL,10000000= 10.00
USDC_SOL (micro-dollars).
Deliver vs receive amountUse
deliver_amountby default — this is the amount the customer inputs (what they are sending).
The response returnsreceive_amountwith fees deducted (in production; no fees in sandbox).
Alternatively, specifyreceive_amountto lock in the amount the recipient receives after fees,
meaning the customer paysreceive_amount+ fees.
Quotes expire in 30 secondsQuotes are valid for 30 seconds. Use the
expires_atfield on the quote response to determine
when a quote expires. Trades or transfers against expired quotes fail.
Create the funding transfer
Endpoint: POST /api/transfers
Reference: Create Transfer
The ACH pull sources from the linked external bank account and deposits into the customer's USD fiat account.
Both source_participants and destination_participants are the customer.
POST /api/transfers
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"quote_guid": "quote_guid",
"transfer_type": "funding",
"external_bank_account_guid": "external_bank_account_guid",
"payment_rail": "ach",
"source_participants": [
{ "type": "customer", "guid": "customer_guid", "amount": 10000 }
],
"destination_participants": [
{ "type": "customer", "guid": "customer_guid", "amount": 10000 }
]
}{
"guid": "transfer_guid",
"transfer_type": "funding",
"customer_guid": "customer_guid",
"quote_guid": "quote_guid",
"external_bank_account_guid": "external_bank_account_guid",
"asset": "USD",
"side": "deposit",
"state": "storing",
"estimated_amount": 10000,
"fee": 0,
"payment_rail": "ach",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Transfer states
| State | Description |
|---|---|
storing | Platform is storing transfer details |
pending | Transfer pending |
holding | Transfer on hold (compliance review) |
reviewing | Transfer under manual review |
completed | Transfer completed |
failed | Transfer failed |
cancelling | Cancellation requested (transient); resolves to failed (failure_code cancelled) or completed |
Webhook Events: transfer.pending, transfer.completed, transfer.failed, transfer.cancelling
Poll GET /api/transfers/{transfer_guid} or subscribe to the transfer.completed webhook until state
transitions to completed.
Both participants are requiredBoth
source_participantsanddestination_participantsare required on funding transfers.
source_participantsidentifies who is sending funds;destination_participantsidentifies who
is receiving funds. Each participant requirestype,guid, andamount.
Related: Transfer Process ·
Transfer Participants
CheckpointThe funding transfer is
completedand USD is available in the customer's fiat account. You are ready to
convert it to USDC_SOL.
7. Convert USD to USDC_SOL
Convert the funded USD to USDC_SOL in the trading account. This settlement funds the source account for the
remittance plan. Create a trading quote, then create the trade against it.
Create the trading quote
Endpoint: POST /api/quotes
Reference: Create Quote
POST /api/quotes
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"product_type": "trading",
"customer_guid": "customer_guid",
"symbol": "USDC_SOL-USD",
"side": "buy",
"deliver_amount": 10000
}{
"guid": "quote_guid",
"product_type": "trading",
"customer_guid": "customer_guid",
"symbol": "USDC_SOL-USD",
"side": "buy",
"receive_amount": 10000000,
"deliver_amount": 10000,
"fee": 0,
"issued_at": "2026-07-19T12:00:00.000Z",
"expires_at": "2026-07-19T12:00:30.000Z",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}
Quotes expire in 30 secondsQuotes are valid for 30 seconds. Use the
expires_atfield on the quote response to determine
when a quote expires. Trades or transfers against expired quotes fail.
Execute the trade
Endpoint: POST /api/trades
Reference: Create Trade
POST /api/trades
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"quote_guid": "quote_guid"
}{
"guid": "trade_guid",
"trade_type": "platform",
"customer_guid": "customer_guid",
"quote_guid": "quote_guid",
"symbol": "USDC_SOL-USD",
"side": "buy",
"state": "storing",
"receive_amount": 10000000,
"deliver_amount": 10000,
"fee": 0,
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Trade states
| State | Description |
|---|---|
storing | Platform is storing trade details |
pending | Trade pending execution |
executed | Trade executed on exchange |
settling | Trade settling |
completed | Trade completed |
cancelled | Trade cancelled |
failed | Trade failed |
Webhook Events: trade.pending, trade.completed, trade.failed
Poll GET /api/trades/{trade_guid} or subscribe to the trade.completed webhook until state
transitions to completed.
Related: Execute Trades
CheckpointThe trade is
completedand USDC_SOL is available in the trading account. This balance is the source for the
remittance plan.
8. Create the recipient counterparty
Create the recipient as a customer-level counterparty, then verify it via watchlist screening. The
counterparty must reach the verified state before you can create its external bank account or the
remittance plan.
Create the counterparty
Endpoint: POST /api/counterparties
Reference: Create Counterparty
POST /api/counterparties
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "individual",
"customer_guid": "customer_guid",
"name": {
"first": "John",
"last": "Doe"
},
"address": {
"city": "Mumbai",
"country_code": "IN"
}
}{
"guid": "recipient_counterparty_guid",
"type": "individual",
"customer_guid": "customer_guid",
"state": "storing",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}
Required fields onlyFor individual counterparties the required fields are
type,name.first,name.last,
address.city, andaddress.country_code. Addcustomer_guidfor customer-scoped counterparties
created with a bank token. The API spec marks onlycountry_codeas required onaddress, but the
Cybrid platform enforcescity+country_codetogether — see
Create a Counterparty.
Corridor-specific fieldsFor Cybrid-managed remittance corridors, the destination country may require additional fields
on the recipient counterparty (date_of_birth,identification_numbers, addressstreet+
subdivision, etc.). Refer to the relevant payout guide for the destination country before
omitting fields in production. See Supported Corridors.
Verify the counterparty
Endpoint: POST /api/identity_verifications
Reference: Create Identity Verification
Counterparty verification screens the recipient's PII through watchlist checks against government sanctions
lists, PEP databases, and adverse media sources. It is an API call that does not require an SDK.
POST /api/identity_verifications
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "counterparty",
"method": "watchlists",
"counterparty_guid": "recipient_counterparty_guid"
}{
"guid": "identity_verification_guid",
"type": "counterparty",
"method": "watchlists",
"counterparty_guid": "recipient_counterparty_guid",
"customer_guid": "customer_guid",
"state": "storing",
"outcome": null,
"failure_codes": [],
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Counterparty states
| State | Description |
|---|---|
storing | Platform is storing counterparty details |
unverified | Counterparty created, awaiting verification |
verified | Counterparty verified via watchlist check |
rejected | Counterparty rejected |
Poll GET /api/identity_verifications/{identity_verification_guid} until state transitions to
completed. When outcome is passed, the counterparty transitions to verified; when failed, inspect
failure_codes (for example watchlist_check_failure, pep_check_failure, media_check_failure) and the
counterparty transitions to rejected. The platform checks counterparty verification results at
one-minute intervals, so completion may take up to one minute.
Webhook Events: identity_verification.waiting, identity_verification.completed
Related: Verify a Counterparty · Creating Counterparties · Sending Cross-Border Payments
9. Create the recipient external bank account
Create the recipient's foreign external bank account tied to the verified counterparty. The example below
uses India (INR over IFSC); required fields vary by destination corridor.
Endpoint: POST /api/external_bank_accounts
Reference: Create External Bank Account
Use raw routing details for foreign payoutsUse
account_kindraw_routing_detailsfor the recipient's foreign bank account and tie it to
the verifiedcounterparty_guid. Thepayment_railandbank_code_typedepend on the
destination corridor —IFSCfor India, and other rails (SPEI,PIX,SEPA, and so on) for
other corridors.
Accept-Version for foreign raw-routing accountsCreating foreign raw-routing external bank accounts requires
Accept-Version: 2025-10-01or
later. Corridor-specific bank-account fields vary by destination; refer to the relevant payout
guide before omitting fields in production.
POST /api/external_bank_accounts
Content-Type: application/json
Accept-Version: 2025-10-01
Authorization: Bearer YOUR_TOKEN
{
"name": "Recipient INR Account",
"account_kind": "raw_routing_details",
"customer_guid": "customer_guid",
"counterparty_guid": "recipient_counterparty_guid",
"asset": "INR",
"counterparty_bank_account_details": [
{
"account_identifier": "account_number",
"payment_rail": "IFSC",
"bank_code": "bank_ifsc_code",
"bank_code_type": "IFSC"
}
]
}{
"guid": "recipient_external_bank_account_guid",
"name": "Recipient INR Account",
"asset": "INR",
"account_kind": "raw_routing_details",
"environment": "sandbox",
"bank_guid": "bank_guid",
"customer_guid": "customer_guid",
"counterparty_guid": "recipient_counterparty_guid",
"state": "storing",
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}
Counterparty must be verifiedThe referenced counterparty must be in the
verifiedstate. Creating an external bank account
against an unverified counterparty fails with theunverified_counterpartyfailure code.
External bank account states
| State | Description |
|---|---|
storing | Platform is storing external bank account details |
completed | External bank account created |
unverified | External bank account created but not yet verified |
failed | External bank account creation failed |
refresh_required | External bank account needs to be refreshed |
deleting | Platform is deleting the external bank account |
deleted | External bank account deleted |
Poll GET /api/external_bank_accounts/{external_bank_account_guid} until state transitions to completed.
Related: Foreign Fiat External Bank Accounts · Add External Bank Accounts
CheckpointThe recipient counterparty is
verifiedand their foreign external bank account iscompleted. You have
everything needed to create and execute the remittance plan.
10. Create and execute the remittance plan
Create a remittance plan with the USDC_SOL trading account as source and the recipient's foreign external bank
account as destination, then execute it. Cybrid converts USDC_SOL to INR and pays out over the local rail.
Create the remittance plan
Endpoint: POST /api/plans
Reference: Create Plan
POST /api/plans
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "remittance",
"customer_guid": "customer_guid",
"source_account": {
"guid": "trading_account_usdc_sol_guid",
"amount": 10000000
},
"destination_account": {
"guid": "recipient_external_bank_account_guid"
},
"purpose_of_transaction": "family_support"
}{
"guid": "plan_guid",
"type": "remittance",
"customer_guid": "customer_guid",
"state": "storing",
"expires_at": "2026-07-19T12:00:30.000Z",
"source_account": {
"guid": "trading_account_usdc_sol_guid",
"type": "trading",
"asset": "USDC_SOL",
"requested_amount": 10000000
},
"destination_account": {
"guid": "recipient_external_bank_account_guid",
"type": "external_bank_account",
"asset": "INR"
},
"stages": [],
"fees": [],
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Plan states
| State | Description |
|---|---|
storing | Platform is storing plan details |
planning | Platform is building the plan |
completed | Plan is ready for execution |
failed | Plan creation failed |
Poll GET /api/plans/{plan_guid} until state transitions to completed, then execute it before it expires.
Webhook Events: plan.planning, plan.completed, plan.failed
Enable the remittance featureContact Cybrid support to enable the remittance feature on your bank before using this operation.
Specify the amount on the source accountYou must specify an amount on either the source or destination account — the amount drives the
plan's quote. If source amount is specified, the plan ensures that amount is debited. If
destination amount is specified, the plan ensures that exact amount is deposited. Includeamount
undersource_accountby default, expressed in the source account asset's base units (USDC_SOL).
Purpose of transaction
purpose_of_transactionis corridor-dependent. Some destinations reject a plan without an
accepted purpose (invalid_purpose_of_transaction). Refer to the destination payout guide for
accepted values.
Supporting documents (corridor-dependent)Some corridors require supporting documents attached to the plan's
supporting_documents(for
example, aninvoicecarrying adocument_reference_number). This is most common for B2B payouts
— see the "Upload a supporting document" step in the CA B2B and US B2B integration guides, and
File Upload, before going live.
Travel rule informationTravel rule information is required only when the originator is not the holder of the trading
account or when the receiver is not the holder of the external bank account.
Reversals may incur feesIf the final withdrawal to the destination bank account fails after currency conversions have
occurred, any reversals may incur fees.
Execute the plan
Endpoint: POST /api/executions
Reference: Create Execution
POST /api/executions
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"plan_guid": "plan_guid"
}{
"guid": "execution_guid",
"type": "remittance",
"plan_guid": "plan_guid",
"customer_guid": "customer_guid",
"state": "storing",
"source_account": {
"guid": "trading_account_usdc_sol_guid",
"type": "trading",
"asset": "USDC_SOL"
},
"destination_account": {
"guid": "recipient_external_bank_account_guid",
"type": "external_bank_account",
"asset": "INR"
},
"stages": [],
"fees": [],
"created_at": "2026-07-19T12:00:00.000Z",
"updated_at": "2026-07-19T12:00:00.000Z"
}Execution states
| State | Description |
|---|---|
storing | Platform is storing execution details |
executing | Platform is executing the plan |
completed | Plan execution completed successfully |
failed | Plan execution failed |
Webhook Events: execution.executing, execution.completed, execution.failed
Poll GET /api/executions/{execution_guid} or subscribe to the execution.completed webhook until state
transitions to completed. On failure, inspect the failure_code (for example compliance_rejection,
invalid_destination, party_name_invalid, external_vendor_error).
Creating an execution initiates the transactionCreating an execution initiates the actual transaction. Ensure the plan has been reviewed and is
ready before executing.
Plans expire quicklyRemittance plans expire (default ~60 seconds,
plan_expirederror). Use theexpires_atfield
on the plan response to confirm when the plan expires, and execute it before then.
Executed amountsThe executed amounts in the response represent the actual amounts transferred, which should match
the quoted amounts from the plan.
Related: Sending Cross-Border Payments · Payout Prices
Webhooks
Subscribe to webhooks for real-time notifications instead of polling. Configure webhook endpoints in your bank
settings. Accounts, deposit bank accounts, workflows, and external bank accounts have no webhook events in this
flow — use polling for those resources.
Webhook EventsThis integration uses the following webhook events:
Event Trigger identity_verification.waitingIdentity verification is waiting for the customer or screening to proceed identity_verification.completedIdentity verification completed (check the outcome) transfer.pendingFunding transfer is pending transfer.completedFunding transfer completed transfer.failedFunding transfer failed transfer.cancellingFunding transfer cancellation requested trade.pendingTrade is pending execution trade.completedTrade completed trade.failedTrade failed plan.planningRemittance plan is being built plan.completedRemittance plan is ready for execution plan.failedRemittance plan creation failed execution.executingPlan execution is in progress execution.completedPlan execution completed execution.failedPlan execution failed See: Webhook Events
Additional resources
- Introduction
- Sending Cross-Border Payments
- Supported Corridors
- Foreign Fiat External Bank Accounts
- Payout Prices
- Plaid Integration
- Verify a Counterparty
- Verify External Bank Accounts
Updated about 16 hours ago

