Payout Destinations

How to register where money goes.

POST /v1/customers/{customerId}/linked_accounts

The same object covers both kinds of destination. destinationType says which one you mean, either BANK_ACCOUNT or WALLET.

What you register

Send the country, the currency and the identifiers. You do not send a rail.

Alfred derives supportedRails[] from what you sent:

You registerYou get back
A CLABE in Mexico[SPEI]
A US routing and account number[ACH, WIRE]
A Hong Kong account[FPS, CHATS]

The fields a given route needs are listed in the route registry, on GET /v1/countries/{country}. Build your form from the registry, because that is what stays current.

{
  "destinationType": "BANK_ACCOUNT",
  "country": "MEX",
  "currency": "MXN",
  "holderName": "Proveedora del Norte S.A. de C.V.",
  "bankAccount": {
    "identifierType": "CLABE",
    "identifier": "012180015555555555"
  }
}

A destination whose route isn't LIVE is refused at registration with route_unsupported. You find out at registration time instead of when you try to pay out.

Alfred verifies who owns the account

You don't declare whether a destination belongs to the customer or to a third party. Alfred decides it during verification, from the provider's account validation and a name match against the customer's verified identity, not from the holder name you send. The result is returned as relationship.relationship_type:

  • BANK_ACCOUNT_OWNERSHIP or WALLET_OWNERSHIP for the customer's own
  • THIRD_PARTY_BENEFICIARY for anyone else

This matters because the third-party endorsement check keys off that value. You can't override it by asserting something different in your request.

Wallets

{
  "destinationType": "WALLET",
  "chain": "SOL",
  "address": "..."
}

A wallet destination needs only the chain and the address.

TRM screens every wallet, and that screening is the whole control. You don't sign a challenge, supply an attestation or complete a proof-of-control step on your side. The TRM result is what moves the relationship to VERIFIED, and it is also what refuses an address.

Wait for VERIFIED

Wait on linked_account.updated reaching relationship_status: VERIFIED.

A payout to a destination in any other state is refused with recipient_not_linked before anything is locked.

StatusCan receive a payout
PENDINGNo, screening is still in progress.
VERIFIEDYes.
REJECTEDNo.
EXPIREDNo.
REVOKEDNo.

EXPIRED matters as much as REJECTED. A relationship has a validity window, so a destination that worked last quarter can stop working without anyone touching it. Integrations that only handle the move from PENDING to VERIFIED tend to miss this case.

The allowed_use_cases field is worth reading too. A destination approved for PAYOUT but not for COLLECTION will refuse a collection, and this field tells you before you try.

Reading and removing

GET    /v1/customers/{customerId}/linked_accounts
GET    /v1/linked_accounts/{linkedAccountId}
DELETE /v1/linked_accounts/{linkedAccountId}

The single read returns the derived supportedRails[], the relationship, the use cases it's approved for and its validity window.

Deleting revokes the relationship. Payouts already in flight still complete, and new ones are refused.

The same counterparty can be linked more than once. That is normal when someone has accounts on different rails or in different currencies.

In sandbox

POST /v1/test/linked_accounts/{linkedAccountId}/simulate_activation

This moves a relationship straight to VERIFIED without the review queue. The review queue is the one step of a payout whose timing you otherwise can't control, so simulating it lets you test a payout flow in a minute instead of waiting on someone else's process.

This path works on sandbox only. On production it returns 404.


Did this page help you?