Skip to main content
BVNK

Migrate fiat payments from v.2

Beta

Payments v.3 API endpoints are in beta and may change as the product evolves.

Parts of fiat on v.3 are not finalized

Fiat payouts are supported on v.3 and share the crypto endpoints, but payment rail selection, the required fields for each local banking network, and the Verification of Payee (VoP) confirmation flow are not finalized yet. This guide shows what is confirmed today and marks every interim mapping.

BVNK is unifying its fiat and crypto payments APIs. Fiat payouts move from /payment/v2/payouts to /payment/v3/payouts, the same endpoint that crypto payouts use, with the same envelope, status model, and webhook taxonomy as crypto payments and Internal Transfers.

Fiat v.2 is already close to the v.3 shape. v.2 payouts have an originator and a beneficiary, an entity object for the payee, and ISO 8601 timestamps. Most of the migration comes down to three changes: amounts become strings anchored on one side of the payment, method changes meaning, and statuses are renamed to match the model shared with crypto.

Benefits of migrating to v.3:

  • One payout endpoint for fiat and crypto: /payment/v3/payouts serves both, and method on the response tells you which one a payout is.
  • One status model: fiat and crypto payouts share the same statuses, including new recall statuses for fiat.
  • Verification as a payout status: VoP is expected to become the PENDING_VERIFICATION status on an existing payout, instead of a 202 response that creates no payout.
  • Payments that link to each other: labels[] and relatedPayments[] tie a refund to the pay-in it corrects.
  • An audit trail on every payout: statusHistory[] records each status change with a timestamp.
v.2 keeps working

v.3 is available alongside v.2. There is currently no mandatory migration or sunset date for the v.2 fiat endpoints, allowing you to migrate each flow individually at your own pace.

Migration scope​

  • In scope: fiat payouts on /payment/v2/payouts, and the fiat payout and pay-in webhooks.
  • Not yet on v.3: receiving fiat pay-ins, refunds, beneficiary pre-verification, proof of payment, and pay-in simulation. Keep using the current endpoints. See Endpoint mapping.
  • Not affected: virtual accounts, wallets, payment rules, payment limits, and the ledger. Transfers between your wallets are managed through Internal Transfers v.3, a separate endpoint.

What changes from v.2​

The concepts v.3 is built on are explained in Payments API v.3. These are the differences from v.2 fiat payouts that drive most of the field mapping in this guide:

  • Two sides, now in blocks: v.2 already described a payout as an originator and a beneficiary, but only the payee lived in its own block, under beneficiary; walletId, amount, and currency sat at the top level. v.3 moves your side into originator too. Direction follows from the endpoint, and payout responses still return direction: "OUT", as in v.2. See Originator and beneficiary.
  • One fixed amount, against any of three currencies: v.2 fixed only the debit. v.3 fixes the amount against exactly one of originator.amount (the debit, matching v.2 behavior), beneficiary.amount (the credit), or checkout.amount (the display value), and derives the other two. See Amounts.
  • Payment method and payment instrument: in v.2, method on the request selected the payment rail, for example SEPA_CT, ACH, or SWIFT. v.3 requests carry no method: BVNK infers fiat versus crypto from beneficiary.paymentInstrument.type and returns method: "FIAT" or "CRYPTO" on responses. For a bank account, paymentInstrument.type is BANK_ADDRESS. How a v.3 payout selects a rail is not specified yet. See Payment methods.
  • Verification of Payee as a status: in v.2, a EUR payout whose beneficiary name fails the VoP check returns 202 with status: "PAYEE_VERIFICATION_REQUIRED", and no payout exists until you confirm it with CONFIRM_BENEFICIARY within five minutes. In v.3, this becomes the PENDING_VERIFICATION status shared with crypto: the payout exists, is fully priced, and waits for confirmation before BVNK sends it. The v.3 confirmation action for fiat, the shape of the name-match result, and the confirmation window are not specified yet. See Payment statuses.
Reading identifiers in v.3

Identifiers ending in Reference are user-supplied, while those ending in Id are issued by BVNK. For example, customerReference and reference are provided by you, whereas customerId, id, contactId, and paymentId are provided by BVNK. This rule applies to all identifiers in the API.

Endpoint mapping​

FlowCurrent endpointv.3 endpointGuide
Create and send a payoutPOST /payment/v2/payoutsPOST /payment/v3/payoutsSend fiat payouts
Get a payoutGET /payment/v2/payouts/{transactionId}GET /payment/v3/payouts/{id}Send fiat payouts
Confirm a payout after a VoP mismatchPOST /payment/v2/payouts/{Id}/actions with CONFIRM_BENEFICIARYExpected: the payout is held at PENDING_VERIFICATION. The confirmation action is not specified yet.Send fiat payouts
Verify a beneficiary before the payoutPOST /platform/v1/beneficiaries/verification, then the X-Verification-Id header on the payoutNo v.3 equivalent yet. Keep using the current endpoint with v.2 payouts.Send payments
Get proof of paymentGET /payment/v2/payouts/{transactionId}/proof-of-paymentNo v.3 equivalent yet. Keep using the v.2 endpoint.—
Receive a fiat pay-inNo endpoint: pay-ins arrive at your virtual accountNo change. Keep receiving fiat at your virtual accounts.Receive fiat pay-ins
Estimate a refund and refund a pay-inPOST /payment/v1/payins/{transactionReference}/refund/estimate
POST /payment/v1/payins/{transactionReference}/refund
No v.3 endpoint. Fiat refunds on v.3 are not finalized. Keep using the current endpoints.Refund payments
Simulate a pay-in in sandboxPOST /payment/v2/payins/simulationNo v.3 equivalent yet. Keep using the v.2 endpoint.—

Only payout creation and retrieval move to v.3 today. Continue using the current endpoints for every other fiat flow while you migrate payouts.

Payout lifecycle​

In v.2, a payout with complete details is created and sent in one call. A EUR payout that fails the VoP name check is the exception: BVNK returns 202, creates no payout, and waits for your confirmation.

In v.3, a payout with complete details is also created and sent in one call. Verification is expected to be a status on the payout rather than a separate response.

One call, plus a confirmation call when the beneficiary name does not match.

Endpoint changes​

Most changes in this section are silent: existing parsing code may keep running but return incorrect values instead of errors. Start with the breaking changes.

Breaking changes​

method changes meaning. In v.2, method is the payment rail, sent on the request and echoed on the response, for example SEPA_CT. In v.3, requests carry no method, and responses return FIAT or CRYPTO. Reporting or routing code that reads the rail from method keeps running but receives FIAT.

dateOfBirth changes format. v.2 takes DD-MM-YYYY, for example 15-01-1990. v.3 takes ISO 8601 YYYY-MM-DD, for example 1990-01-15. Convert stored dates before you send them.

type drops the :fiat suffix. v.2 returns type: "payment:payout:fiat". v.3 returns type: "payment:payout" for fiat and crypto alike. Filter on method: "FIAT" instead.

Status values change. ON_HOLD becomes HELD, and CANCELLED splits into REJECTED and EXPIRED. Handlers that compare status strings fall through silently. See the Status reference.

Conventions and fields​

Changev.2v.3Notes
Money amountsNumbers, for example 1000.5Strings, for example "1000.50"Applies to all monetary fields, including fees.
TimestampsISO 8601ISO 8601Unchanged.
Date of birthDD-MM-YYYYYYYY-MM-DDSee Breaking changes.
WalletwalletId at the top leveloriginator.walletIdoriginator.currency is derived from the wallet.
Amount and currencyamount and currency at the top levelOne amount anchor, plus beneficiary.currencySet originator.amount to keep v.2 behavior. See Amounts.
Response amountsA flat amountrequired.amount is what you asked for,
actual.amount is what was paid
Applies on originator, beneficiary, and checkout, each a {currency, amount} object. Fees carry estimated and actual, except customerFee, which carries required and actual. originator.actual shows the debit once the quote is accepted, and every other actual has an amount of 0.00 until the payout reaches COMPLETED. Requests are unchanged.
Referencereference, optionalreference, requiredReusing a reference returns a validation error and does not create a duplicate.
Payment methodmethod: the payment rail, on the request and responsemethod: FIAT or CRYPTO, on the response onlyRail selection is not specified yet. See Breaking changes.
Resource typepayment:payout:fiatpayment:payoutShared with crypto payouts.
Directiondirection: "OUT" on responsesdirection: "OUT" on responsesUnchanged.
Statusesv.2 payout enumThe v.3 enum shared with cryptoSee the Status reference.
Bank detailsbeneficiary.bankAccount, including intermediaryBanks[]beneficiary.paymentInstrument with "type": "BANK_ADDRESS", including intermediaryBanks[]bank.identificationCode becomes bank.bic, and bank.country moves into bank.address.
Tracking detailstrackingDetails[] on v.2 webhooks, with uetr, imad, omad, traceNumber, and externalTransactionIdtrackingDetails[] with on-chain fields: hash, network, amount, currency, and confirmedAtBank-rail tracking is not yet modeled.
Status historyNot availablestatusHistory[], an array of {status, timestamp}New. No migration required.
labels[]
relatedPayments[]
Not availableNew. Classify why a payment exists, and link it to its counterpartSee Migrate pay-ins and refunds.
metadataUp to five string valuesUnchangedNo migration required.

Hawk authentication works the same way in v.3 as in v.2. No authentication changes are required.

Fields not yet modeled in v.3​

The v.3 spec does not yet define these v.2 fiat fields. Until it does, treat any v.3 shape for them as provisional:

  • Payment rail: the v.2 request method, for example SEPA_INST or FEDWIRE.
  • Remittance information: beneficiary.remittanceInformation, the payment reference your payee sees on their statement.
  • Requester IP address: requestDetails.originator.ipAddress. The closest v.3 field is beneficiary.requesterIpAddress, which v.3 describes as the IP address of the customer receiving the payout. Confirm with BVNK which one applies before mapping it.
  • Verification of Payee: the X-Verification-Id header, the CONFIRM_BENEFICIARY action, and the verificationResult object.
  • Bank-rail tracking: the uetr, imad, omad, traceNumber, and externalTransactionId fields on trackingDetails[].

Status reference​

v.3 payouts use one status model for fiat and crypto. Several statuses are conditional and only occur if the corresponding step applies, so a payout that needs no approval or verification goes straight to PROCESSING. For the full list of v.3 statuses and the payout status diagram, see Payment statuses.

v.2 statusv.3 statusNotes
PENDINGNo direct equivalentA new v.3 payout is at PROCESSING, or at a conditional status such as PENDING_APPROVAL or PENDING_VERIFICATION.
PAYEE_VERIFICATION_REQUIRED (202 response, no payout yet)PENDING_VERIFICATION (conditional, expected)The payout exists and waits for confirmation. See What changes from v.2.
PENDING_APPROVALPENDING_APPROVAL (conditional)Unchanged. A member of your team must approve the payout.
PROCESSINGPROCESSINGUnchanged.
ON_HOLDHELDRenamed. Held for compliance reasons. Reversible: a held payout can return to PROCESSING.
COMPLETEDCOMPLETEDUnchanged. The beneficiary bank can still return the funds afterwards, which moves the payout to RETURNED.
CANCELLEDREJECTED or EXPIREDSplit in two. REJECTED when your team declines the approval, EXPIRED when the approval is not given within the seven-day window. Neither is retryable: create a new payout.
EXPIREDEXPIREDUnchanged. In v.2, this status appears on webhooks only.
FAILEDFAILEDUnchanged. The payout was attempted from PROCESSING and did not succeed.
RETURNEDRETURNED (conditional)Unchanged, and now also reached when a recall succeeds. Terminal.
❌PENDING_RECALL (conditional)New. A recall has been requested on a payout that is already in flight.
❌RECALL_REJECTED (conditional)New. Terminal. The beneficiary bank declined the recall, and the payout stands.

Migrate payouts​

The v.2 payout below sends 1,000.50 EUR over SEPA to an individual payee. In v.3, your wallet and the amount move into originator, the currency moves into beneficiary, and the rail is no longer sent.

Request: POST /payment/v2/payouts
{
"walletId": "a:24092324785677:o6rhCEZ:1",
"amount": 1000.5,
"currency": "EUR",
"method": "SEPA_CT",
"reference": "PAYOUT-2025-001",
"beneficiary": {
"remittanceInformation": "Invoice INV-2025-0412",
"entity": {
"type": "INDIVIDUAL",
"firstName": "Anna",
"lastName": "Schmidt",
"dateOfBirth": "15-01-1990",
"relationshipType": "THIRD_PARTY",
"address": {
"addressLine1": "Hauptstrasse 42",
"city": "Munich",
"postCode": "80331",
"country": "DE"
}
},
"bankAccount": {
"accountNumber": "DE89370400440532013000",
"bank": {
"identificationCode": "COBADEFFXXX",
"country": "DE"
}
}
},
"fees": {
"customerFee": {
"amount": 5,
"currency": "EUR"
}
},
"metadata": {
"invoiceId": "INV-2025-0412"
},
"requestDetails": {
"originator": {
"ipAddress": "77.71.188.87"
}
}
}

Request field changes:

v.2 fieldv.3 fieldNotes
walletIdoriginator.walletIdMoved into the originator block. The wallet determines originator.currency automatically.
amount: numberoriginator.amount: "string"Numbers become strings. Fixes the debit, as in v.2. Set only one amount anchor.
currencybeneficiary.currencyThe currency the payee receives.
method❌Not accepted on v.3 requests. Rail selection is not specified yet.
referencereferenceNow required. Reusing a reference returns a validation error.
beneficiary.remittanceInformation❌Not yet modeled.
beneficiary.entity.typebeneficiary.entity.typeUnchanged. Values are still INDIVIDUAL or COMPANY.
...firstName
lastName
beneficiary.entity.firstName
.lastName
Unchanged. INDIVIDUAL entities only.
...legalNamebeneficiary.entity.legalNameUnchanged. COMPANY entities only.
...dateOfBirth: "15-01-1990"beneficiary.entity.dateOfBirth: "1990-01-15"Format changes from DD-MM-YYYY to YYYY-MM-DD.
...relationshipTypebeneficiary.entity.relationshipTypeUnchanged. Values are still SELF_OWNED or THIRD_PARTY.
...address.*beneficiary.entity.address.*Same field names. v.3 requires only country, where v.2 also required addressLine1 and city. Keep sending the full address: banking partners may reject payouts with incomplete details.
❌beneficiary.entity.registrationNumberNew and optional. COMPANY entities only.
beneficiary.bankAccountbeneficiary.paymentInstrumentRenamed. Add "type": "BANK_ADDRESS", which marks the payout as fiat.
...accountNumberbeneficiary.paymentInstrument.accountNumberUnchanged. Set accountNumberFormat, for example IBAN, SCAN, or ABA, to match.
...accountTypebeneficiary.paymentInstrument.accountTypeUnchanged. Mandatory for USD payouts.
...bank.identificationCodebeneficiary.paymentInstrument.bank.bicRenamed. For a local USD payout, send the ABA routing number in bank.nid.
...bank.nidbeneficiary.paymentInstrument.bank.nidUnchanged.
...bank.countrybeneficiary.paymentInstrument.bank.address.countryMoved into the bank address.
...bank.addressbeneficiary.paymentInstrument.bank.addressUnchanged.
...intermediaryBanks[]beneficiary.paymentInstrument.intermediaryBanks[]Moved into the payment instrument. Each entry carries name, bic, nid, branchCode, and address.
❌beneficiary.contactIdNew. References a stored contact instead of an inline entity. Send contactId or entity, never both.
❌beneficiary.customerId
beneficiary.customerReference
New. The BVNK-issued ID of the customer, and your own reference for them.
fees.customerFee.amount: numberfees.customerFee.amount: "string"Numbers become strings. Still deducted from the payee's amount.
metadatametadataUnchanged. Up to five string values.
requestDetails.originator.ipAddress❌No originator-side IP field in v.3. The closest field is beneficiary.requesterIpAddress. See Fields not yet modeled in v.3.
X-Verification-Id header❌Not specified yet.

Response field changes:

v.2 fieldv.3 fieldNotes
ididUnchanged. Still a UUID.
type: "payment:payout:fiat"type: "payment:payout"Shared with crypto payouts. Filter on method instead.
method: "SEPA_CT"method: "FIAT"Same field name, different purpose. v.2 returned the rail; v.3 returns the payment method.
direction: "OUT"direction: "OUT"Unchanged.
statusstatusValues change. See the Status reference.
referencereferenceUnchanged.
fees.processingFee.{amount, currency}fees.processingFee.estimated.{currency, amount}
fees.processingFee.actual.{currency, amount}
Numbers become strings. estimated is the fee as quoted; actual is the fee charged, with an amount of 0.00 until COMPLETED.
fees.customerFee.{amount, currency}fees.customerFee.required.{currency, amount}
fees.customerFee.actual.{currency, amount}
Numbers become strings. required is the fee you set; actual is the fee deducted, with an amount of 0.00 until COMPLETED. null when not supplied.
❌fees.networkFeeCrypto payouts only. Omitted on fiat payouts.
originator.amount: numberoriginator.required.amount
originator.actual.amount
Numbers become strings. required is what you asked for, and actual is what was debited, set once the quote is accepted.
originator.currency
originator.walletId
originator.required.currency
originator.walletId
currency moves inside the amount objects. walletId is unchanged.
originator.customerId❌Not returned on the v.3 payout originator.
originator.entity❌Not returned on the v.3 payout originator.
beneficiary.amount: numberbeneficiary.required.amount
beneficiary.actual.amount
Numbers become strings. actual has an amount of 0.00 until COMPLETED.
beneficiary.currencybeneficiary.required.currencyMoves inside the amount objects.
beneficiary.remittanceInformation❌Not yet modeled.
beneficiary.entitybeneficiary.entitySame fields. dateOfBirth uses YYYY-MM-DD.
beneficiary.bankAccountbeneficiary.paymentInstrument"type": "BANK_ADDRESS", with the same field changes as on the request. accountNumberFormat is not returned.
createdAt
updatedAt
createdAt
updatedAt
Unchanged. ISO 8601.
metadatametadataUnchanged.
requestDetails❌Not returned.
❌conversionNew. null when the payout involves no currency conversion.
❌checkoutNew. The display currency block shared with crypto payouts. Mirrors the originator currency and amount when you set no display currency.
❌instructions[]New on the shared schema. Always empty on payouts.
❌flowNew. default for the standard flow.
❌trackingDetails[]New on the payout resource. The v.3 entries carry on-chain fields only. Bank-rail tracking is not yet modeled.
❌statusHistory[]New. Array of {status, timestamp} on the payout.
❌labels[]
relatedPayments[]
New. Populated when this payout relates to another payment, for example a refund.
202 body: Id, status, requiredAction, verificationResult❌Expected to be replaced by status: "PENDING_VERIFICATION" on the payout. The name-match result shape is not specified yet.

For the full v.3 fiat payout request and response, see Send fiat payouts.

Migrate pay-ins and refunds​

Pay-ins

v.2 has no endpoint to create a fiat pay-in: funds arrive at your virtual account, and BVNK sends a payment:v2:payin:status-change webhook. This does not change. POST /payment/v3/payins creates payment links whose deposit instructions are crypto only (instructions[].type is always CRYPTO), so keep receiving fiat at your virtual accounts. See Receive fiat pay-ins.

Refunds

Today, you refund a fiat pay-in with the refund endpoints described in Refund payments. In v.3, a refund is not a separate endpoint or event. It is an ordinary payout that carries labels: ["REFUND"], with relatedPayments[] linking back to the pay-in it corrects, and the pay-in linking forward to the refund. How a fiat refund payout is created on v.3, and whether funds return to the original bank account automatically, is not finalized. Keep using the current refund endpoints.

Consolidate webhooks​

v.3 uses one status-change event per resource, shared by fiat and crypto. The status field indicates what happened, method tells fiat from crypto, and labels[] explains any unusual circumstances.

v.2 eventv.3 eventNotes
payment:v2:payout:status-changepayment:v3:payout:status-changeThe same event as crypto payouts. data.method returns FIAT instead of the rail, data.type drops the :fiat suffix, and data.direction is not sent. Read the progression from statusHistory[]. The fiat bank-detail shape is not finalized.
payment:v2:payin:status-changepayment:v3:payin:status-changePlanned. The v.3 webhook spec states that fiat shares this event, but the fiat pay-in payload is not finalized. Keep your v.2 handler in place.

Full event schemas and example payloads live in the Payments v.3 webhooks reference.

Migration checklist​

Follow these steps in order. The first three steps help prevent silent data errors.

  1. Audit the breaking changes. Find every place your integration reads the rail from method, filters on type: "payment:payout:fiat", or compares payout status strings, and plan the v.3 replacements.
  2. Update money and date handling. Every amount, including fees, is now a string. Convert dateOfBirth from DD-MM-YYYY to YYYY-MM-DD.
  3. Map statuses. Rename ON_HOLD to HELD, split CANCELLED into REJECTED and EXPIRED, and handle the new PENDING_RECALL and RECALL_REJECTED statuses.
  4. Update payout creation. Replace POST /payment/v2/payouts with POST /payment/v3/payouts.
    • Move walletId into originator.walletId, amount into originator.amount, and currency into beneficiary.currency.
    • Remove method from the request.
    • Always send a unique reference.
  5. Plan for the VoP change. Prepare to handle PENDING_VERIFICATION on an existing payout instead of a 202 response, once BVNK publishes the fiat confirmation action.
  6. Consolidate your webhook handlers. Subscribe to payment:v3:payout:status-change and branch on status, method, and labels[].
  7. Leave the other fiat flows on their current endpoints. Pay-ins, refunds, beneficiary pre-verification, proof of payment, and pay-in simulation have no v.3 equivalent yet.
  8. Leave your v.2 integration in place until each flow is cut over. Nothing is being switched off, so v.2 traffic keeps working while you migrate.

Related guides:

Was this page helpful?