Skip to main content
A beneficiary is the destination account — bank or e-wallet — that receives payouts on behalf of your customer. This guide walks you through onboarding a beneficiary end-to-end using the latest API versions.

Before You Start

PrerequisiteDetail
Bearer tokenIssued via POST /v1/oauth/token
Approved customerCustomer customer_status must be APPROVED
Reference dataCountry, bank, and branch identifiers (covered in Step 1)

Onboarding Flow

┌─────────────────────────────────────┐
│  1. Fetch Reference Data            │
│     Countries → Methods → Branches  │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│  2. Create Beneficiary              │
│     POST /v3/beneficiaries          │
│     → status: PROCESSING           │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│  3. Upload Documents (if needed)    │
│     POST /v1/beneficiaries/:id/docs │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│  4. Wait for beneficiary.status     │
│     webhook → ACTIVE or REJECTED   │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│  5. Verify & Confirm                │
│     GET /v3/beneficiaries/details   │
└──────────────┬──────────────────────┘

         Ready to transact

Step 1 — Fetch Reference Data

Three catalogue endpoints feed into beneficiary creation. These are V1-only — no newer versions exist.

1a. List Supported Countries

GET /v1/beneficiaries/countries
Returns every payout-eligible country with supported methods and phone validation rules.
{
  "data": [
    {
      "id": 4,
      "code": "AUS",
      "name": "Australia",
      "currency_code": "AUD",
      "phone": { "code": "+61", "min_length": 9, "max_length": 9 },
      "available_methods": ["BANK"],
      "flag": "https://flagcdn.com/au.svg"
    }
  ]
}
What you need from this: code (ISO alpha-3) → maps to country and currency in the create call. available_methods tells you whether to build a bank form or e-wallet form.

1b. List Bank / E-Wallet Identifiers

GET /v1/beneficiaries/methods?country_code=BGD&method=BANK
ParameterRequiredValues
country_codeYesISO alpha-3 (from Step 1a)
methodYesBANK or E_WALLET
{
  "data": [
    {
      "id": 1461,
      "name": "AB BANK LTD.",
      "method": "BANK",
      "has_branch": true
    }
  ]
}
What you need from this: id → used as bank_routing[].number with scheme BANK_IDENTIFIER. Check has_branch — if true, proceed to 1c.

1c. List Branch Identifiers (conditional)

Only call this if has_branch is true from Step 1b.
GET /v1/beneficiaries/methods/{method_id}/branches
{
  "data": [
    {
      "id": 21513,
      "name": "SITAKUNDA",
      "branch_code": "020157391"
    }
  ]
}
What you need from this: id → used as bank_routing[].number with scheme BRANCH_IDENTIFIER.

Step 2 — Create the Beneficiary

POST /v3/beneficiaries
This is the primary creation endpoint. The payload has a fixed common section plus a polymorphic destination section (bank or e-wallet).

Request Structure

{
  // ── Common (always required) ──────────────────
  customer_id            // uuid — must be APPROVED
  country                // ISO alpha-3
  currency               // e.g. "BDT"
  counter_party          // "FIRST_PARTY" or "THIRD_PARTY"
  account_holder         // who gets paid (Individual or Business)
  account_holder_address // their address
  receiver_meta_data     // purpose, relationship, nationality
  developer_fee          // your markup
  deposit_instruction    // how funds arrive (USDC / POLYGON)
  refund_instruction     // where refunds go

  // ── Destination (one of) ──────────────────────
  bank_account + bank_routing + bank_address   // Bank path
  e_wallet                                     // E-Wallet path

  // ── Optional ──────────────────────────────────
  settlement_config      // auto-settle toggle
}

Account Holder

The account_holder object is polymorphic based on type. Individual:
{
  "type": "INDIVIDUAL",
  "first_name": "John",
  "last_name": "Doe",
  "email": "john.doe@example.com",
  "phone": "+8801912244626"
}
Business:
{
  "type": "BUSINESS",
  "business_name": "Acme Corp",
  "email": "contact@acmecorp.com",
  "phone": "+8801912244626"
}

Account Holder Address

{
  "account_holder_address": {
    "street_line_1": "42 Wallaby Way",
    "city": "Dhaka",
    "state": "BD-13",
    "postcode": "1212",
    "country": "BGD"
  }
}
state uses ISO 3166-2 subdivision codes. Fetch valid values from GET /v1/countries/{country_code}/subdivisions.

Receiver Metadata

{
  "receiver_meta_data": {
    "transaction_purpose_id": 1,
    "transaction_purpose_remarks": null,
    "occupation_id": 5,
    "occupation_remarks": "Software Engineer",
    "relationship": "FAMILY_MEMBER",
    "relationship_remarks": "Family & Friends",
    "nationality": "AUS",
    "govt_id_number": "JG1121316A",
    "govt_id_issue_date": "2024-12-30",
    "govt_id_expire_date": "2027-12-30"
  }
}
transaction_purpose_id → fetch from GET /v1/transaction-purposes?type=INDIVIDUAL or ?type=BUSINESS. occupation_id → fetch from GET /v1/occupations. Relationship values by account holder type: When account_holder.type is INDIVIDUAL:
CategoryValues
EmploymentEMPLOYEE, FREELANCER, GIG_WORKER, CONTRACTOR
PersonalFAMILY_MEMBER, FRIEND, SELF, OTHER
CommercialCUSTOMER, AFFILIATE
When account_holder.type is BUSINESS:
CategoryValues
Supply chainSUPPLIER, VENDOR, SERVICE_PROVIDER, MERCHANT
Corporate structureSUBSIDIARY, PARENT_COMPANY, AFFILIATE_BUSINESS
FinancialBANK_ACCOUNT, BROKER, EXCHANGE, WALLET
OtherCONTRACTOR, OTHER

Developer Fee

Your markup on each payout through this beneficiary.
{
  "developer_fee": {
    "fixed": 0.02,
    "percentage": 0.01
  }
}
Both components are applied: total fee = fixed + (percentage × payout amount).

Deposit & Refund Instructions

Deposit instruction — how source funds arrive:
{
  "deposit_instruction": {
    "currency": "USDC",
    "rail": "POLYGON"
  }
}
Refund instruction — where failed payouts are returned:
{
  "refund_instruction": {
    "wallet_address": "0x1b577931C1cC2765024bFbafad97bCe14FF2e87F",
    "currency": "USDC",
    "rail": "POLYGON"
  }
}

Settlement Config (optional)

{
  "settlement_config": {
    "auto_settlement": true
  }
}
When true, payouts execute automatically once funds hit the liquidation address. When false, you must call the settle endpoint manually.

Destination: Bank Account

Add three fields: bank_account, bank_routing, and bank_address. bank_account:
{
  "bank_account": {
    "bank_name": "Commonwealth Bank of Australia",
    "number": "1234572211",
    "scheme": "LOCAL",
    "type": "SAVINGS"
  }
}
FieldValues
schemeLOCAL, SWIFT
typeCHECKING, SAVINGS, CURRENT
bank_routing: An array — most corridors need multiple routing entries.
{
  "bank_routing": [
    { "scheme": "SWIFT", "number": "CLNOUS66BRX" },
    { "scheme": "BANK_IDENTIFIER", "number": 160 },
    { "scheme": "BRANCH_IDENTIFIER", "number": 8 }
  ]
}
SchemeWhen to use
SWIFTInternational transfers — BIC/SWIFT code
ACHUS domestic — ACH routing number
WIREUS domestic — Fedwire routing number
IBANEurope, Middle East — IBAN
BSBAustralia — Bank/State/Branch code
IFSCIndia — IFSC code
BANK_IDENTIFIERGeneric — use id from /v1/beneficiaries/methods
BRANCH_IDENTIFIERGeneric — use id from /v1/beneficiaries/methods/:id/branches
BANK_CODECountry-specific bank code
BRANCH_CODECountry-specific branch code
TRANSIT_NUMBERCanada — transit number
bank_address:
{
  "bank_address": {
    "street_line_1": "Ground Floor Tower 1, 201 Sussex Street",
    "city": "Dhaka",
    "state": "BD-13",
    "postcode": "1212",
    "country": "BGD"
  }
}

Destination: E-Wallet

Replace the three bank fields with a single e_wallet object.
{
  "e_wallet": {
    "scheme": "BKASH",
    "number": "+8801688502814"
  }
}
scheme values come from the name field in the /v1/beneficiaries/methods?method=E_WALLET response.

Response

{
  "data": {
    "beneficiary_id": "0254b433-e47d-412e-844b-b735c4bbba74"
  }
}
Store this beneficiary_id — you’ll need it for payouts, document uploads, and detail lookups.

De-Duplication Rules

Fin rejects duplicate beneficiaries with a 409 Conflict. Uniqueness is determined by:
DestinationFields checked
Bankbank_account.scheme + bank_account.number + bank_routing.scheme + bank_routing.number
E-Wallete_wallet.scheme + e_wallet.number
The 409 response includes the existing beneficiary_id so you can reuse it instead of creating a new one.

Country-Specific Validation

CountryRule
GBRBank account number must match ^[0-9]{4,9}$ for both LOCAL and SWIFT schemes

Step 3 — Upload Documents (Optional)

POST /v1/beneficiaries/{beneficiary_id}/documents
Multipart form-data. Use arbitrary field names. Allowed types: PDF, JPG/JPEG, PNG.
{
  "data": {
    "files": [
      { "invoice1": "/wKbvfH5E_Invoice.pdf" },
      { "invoice2": "/XUOdWacK_Invoice.jpg" }
    ]
  }
}
The returned URIs can be attached to transfer payouts later via the attachments array in POST /v1/transactions/transfer-payout.

Step 4 — Verify & Confirm

Fetch Beneficiary Details

GET /v3/beneficiaries/details?customer_id={id}&beneficiary_id={id}
Confirm the beneficiary was created correctly. The response includes the full record — all fields you submitted plus system-generated values. Key fields to verify:
FieldWhat to check
activeBoolean — controlled by you via PATCH /v1/beneficiaries
statusEnum — controlled by Fin’s internal validation
deposit_instruction.liquidation_addressSystem-assigned — this is where you send USDC to trigger a payout
bank_routing[].nameSystem-resolved bank/branch names from the identifiers you passed

Eligibility for Transactions

A beneficiary can receive payouts only when both conditions are met:
FieldRequired valueControlled by
activetrueYou (via PATCH /v1/beneficiaries)
statusACTIVEFin (system-managed)
These are independent fields. active is your on/off switch. status reflects Fin’s validation outcome. status values:
StatusMeaning
PROCESSINGBeneficiary is being validated by Fin
ACTIVEFin validation passed
INACTIVEFin has deactivated the beneficiary
REJECTEDFin validation failed — cannot be used
A newly created beneficiary starts in PROCESSING with active = true. Listen for the beneficiary.status webhook to know when Fin transitions it to ACTIVE or REJECTED.

Managing Beneficiaries

List All Beneficiaries for a Customer

GET /v1/customers/{customer_id}/beneficiaries
Returns all beneficiaries (active and inactive) for the given customer. Useful for building a beneficiary picker in your UI.

Deactivate / Reactivate

PATCH /v1/beneficiaries
{
  "beneficiary_id": "0254b433-e47d-412e-844b-b735c4bbba74",
  "active": false
}
This endpoint toggles the active flag only — it does not change status. Setting active: false makes the beneficiary ineligible for payouts even if status is ACTIVE. Set active: true to re-enable.
Note: If Fin has set status to REJECTED or INACTIVE, the beneficiary cannot transact regardless of the active flag. For REJECTED beneficiaries, create a new one instead.

Webhooks

Subscribe to these events to track beneficiary lifecycle changes asynchronously.
EventFires when
beneficiary.createdBeneficiary successfully created
beneficiary.statusStatus changes (PROCESSINGACTIVE, ACTIVEINACTIVE, PROCESSINGREJECTED)
beneficiary.liquidation.depositUSDC hits the beneficiary’s liquidation address
All webhook payloads include HMAC signatures for verification.

beneficiary.created

{
  "event": {
    "id": "85804b3a-bf18-4d87-94f3-f7c45e66868e",
    "type": "beneficiary.created",
    "event_reference_id": "4d715f20-f704-45e0-af56-19ade318e852",
    "created_at": "2025-12-10T10:36:18.279837Z",
    "sandbox_mode": true
  },
  "data": {
    "beneficiary_id": "4d715f20-f704-45e0-af56-19ade318e852",
    "customer_id": "efb54adf-b7f4-4716-80e3-806e11f20b7b",
    "type": "INDIVIDUAL",
    "active": true
  }
}

beneficiary.liquidation.deposit

Fires when any transfer hits the liquidation address — useful for confirming funds arrival before payout execution.
{
  "data": {
    "beneficiary_id": "5b4ea7ee-9d40-44b3-b857-dd5a890b9313",
    "customer_id": "bea5a6c1-0611-44c6-8c29-a6608e76916c",
    "amount": 3,
    "liquidation_address": "0xade8141fd1aef58dc0a5365a32a6cfe95904c08f",
    "txn_hash": "0x780823...",
    "type": "INDIVIDUAL",
    "active": true
  }
}

Error Reference

StatusCauseAction
400Field validation failedCheck errors array for field-level details
401Invalid / expired tokenRe-authenticate via POST /v1/oauth/token
409Duplicate beneficiaryUse the returned beneficiary_id from the error response
422Request format errorCheck required fields and data types

V3 vs V2 — Key Differences

Create Beneficiary

AspectV2 (POST /v2/beneficiaries)V3 (POST /v3/beneficiaries)
customer_id formatFree string (e.g., cust_123456)UUID (strict)
counter_partyNot supportedRequired — FIRST_PARTY / THIRD_PARTY
occupation_remarks in receiver_meta_dataRequiredOptional
Response body{ data: { id, status } }{ data: { beneficiary_id } }
Response code200200

Fetch Beneficiary Details

AspectV2 (GET /v2/beneficiaries/details)V3 (GET /v3/beneficiaries/details)
status fieldNot presentPROCESSING / ACTIVE / INACTIVE / REJECTED
counter_party fieldNot presentPresent
bank_routing[].numberNot returned (only name)Returned alongside name
Recommendation: Use V3 for all new integrations. V2 remains available but lacks counter-party tracking and the explicit status field.

What’s Next

With the beneficiary onboarded, you can:
  • Send a single payoutPOST /v1/transactions/transfer-payout
  • Send batch payoutsPOST /v1/batch/transactions/commit
  • Preview feesPOST /v1/fee-calculation (pass beneficiary_id for developer-fee-aware calculations)
  • Check FX ratesGET /v1/fx-rate?currency_code={code}