Skip to main content

Documentation Index

Fetch the complete documentation index at: https://developer.fin.com/llms.txt

Use this file to discover all available pages before exploring further.

A beneficiary is a destination account (bank or e-wallet) that receives payouts on behalf of your customer. This guide walks you through onboarding a beneficiary 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

Beneficiary Onboarding Flow 1

Step 1: Fetch Reference Data

Three catalogue endpoints feed into beneficiary creation. These are V1-only and 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?method=BANK&scheme=LOCAL&currency=BDT&country_code=BGD
ParameterRequiredValues
methodYesBANK or E_WALLET
schemeYesLOCAL or SWIFT
currencyYesDestination currency (from Step 1a)
country_codeYesISO alpha-3 (from Step 1a)
{
  "data": [
    {
      "id": 160,
      "name": "AB BANK",
      "method": "BANK",
      "has_branch": true
    },
    {
      "id": 161,
      "name": "EASTERN 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": "BANANI",
      "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      // settlement method (true or false)
}

Counter Party

The counter_party field declares the relationship between the customer and the beneficiary receiving the payout.
ValueMeaningExample
FIRST_PARTYThe customer is paying themselves. The beneficiary is the same entity as the customer.A business withdrawing funds to its own bank account in another country
THIRD_PARTYThe customer is paying someone else. The beneficiary is a different individual or entity.A business paying a supplier, employee, or freelancer
This field is required in V3 and drives compliance screening. Third-party payouts may be subject to additional checks depending on the corridor.

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": "Fin.com",
  "email": "contact@fin.com",
  "phone": "+8801912244626"
}

Account Holder Address

{
  "account_holder_address": {
    "street_line_1": "Road 11",
    "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: Relationship enums varies by the sender and receiver types. Lern more about sender and beneficiary relationship.

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 defines how source funds arrive:
{
  "deposit_instruction": {
    "currency": "USDC",
    "rail": "POLYGON"
  }
}
Refund instruction defines where failed payouts are returned:
{
  "refund_instruction": {
    "wallet_address": "0x1b577931C1cC2765024bFbafad97bCe14FF2e87F",
    "currency": "USDC",
    "rail": "POLYGON"
  }
}
Only Polygon rail and USDC currency is available now. USDT is coming soon.

Settlement Config (optional)

{
  "settlement_config": {
    "auto_settlement": true
  }
}
Fin.com offers two settlement methods for payouts: Automatic settlement (auto_settlement: true): Fin.com initiates the fiat payout automatically when crypto is received in the beneficiary’s liquidation address. Programmatic settlement (auto_settlement: false): You trigger the payout yourself using the Settle a Transfer endpoint or the Execute Batch Transfer endpoint.

Programmatic settlement with prefunded balance

If you maintain prefunded balances with Fin.com, you must always trigger payouts programmatically. Use either the Settle a Transfer endpoint or the Execute Batch Transfer endpoint. This applies regardless of the beneficiary’s auto_settlement value (true or false).

Programmatic settlement without a prefunded balance

Set auto_settlement to false for the beneficiary. Fin.com will hold the crypto in the liquidation address until you trigger the payout using either the Settle a Transfer endpoint or the Execute Batch Transfer endpoint. Learn more about Funding & Balances.
Pre-funding is required for all local currency payouts (e.g., BDT, INR, EUR).

Destination: Bank Account

Add three fields: bank_account, bank_routing, and bank_address. bank_account:
{
  "bank_account": {
    "bank_name": "Eastern Bank Ltd.",
    "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_IDENTIFIERUse id from /v1/beneficiaries/methods
BRANCH_IDENTIFIERUse 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, Road no 11",
    "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 List Bank Identfier response. Select E_WALLET in query parameter to get the list of E-Wallets.

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 Create a Transfer payload.

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 with 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.createdA new beneficiary is created
beneficiary.statusA beneficiary’s status changes
beneficiary.liquidation.depositUSDC hits the beneficiary’s liquidation address
All webhook payloads include HMAC signatures for verification. Learn how to verify webhook signatures.

beneficiary.created

Triggered when a new beneficiary is created in the system. Use this to confirm the beneficiary was registered and to capture the beneficiary_id on your end.
{
  "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
  }
}
At this point the beneficiary’s status is PROCESSING. Wait for the beneficiary.status webhook before attempting any payouts.

beneficiary.status

Triggered when a beneficiary’s status changes. This is the primary webhook you should listen for to know when a beneficiary is ready to receive payments. Status values:
StatusMeaning
PROCESSINGBeneficiary creation is in progress
ACTIVEBeneficiary is verified and ready to receive payments
INACTIVEBeneficiary has been deactivated
REJECTEDBeneficiary was rejected during verification
Common transitions:
FromToWhat it means
PROCESSINGACTIVEVerification passed. You can now send payouts.
PROCESSINGREJECTEDVerification failed. Create a new beneficiary.
ACTIVEINACTIVEBeneficiary deactivated by Fin.

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": "0x7808238a69057600f0c8e291ffbfde87a74fb81b32fc583231352147770e2751",
    "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 USDC to the beneficiary liquidation address
  • Send a single payoutPOST /v1/transactions/transfer-payout , if the settlement_config is false.
  • Send batch payoutsPOST /v1/batch/transactions/commit , if the settlement_config is false.
  • Preview feesPOST /v1/fee-calculation (pass beneficiary_id for developer-fee-aware calculations)
  • Check FX ratesGET /v1/fx-rate?currency_code={code}