CorebanqCorebanq Developer Docs
Transfersv2Transfers

Finalize an existing v2 draft transfer

Finalizes a transfer created by `POST /v2/transfers/create`. The client may send the full `CreatePaymentRequestV2` again, or omit the body and let the server reuse the finalize-shaped payload stored on the draft. V2 request metadata uses `metadata`, not legacy `meta`. Cross-currency transfers require a locked top-level `quote_id` from `POST /v2/transfers/quotes`. Repeated and concurrent calls are deduplicated by the draft `transfer_id`: the finalized transfer uses a server-derived `draft-finalize:` idempotency key, while the draft superseded link is updated under a row lock and preserved on retries.

POST
/v2/transfers/{transfer_id}/finalize

Authorization

BearerAuth
AuthorizationBearer <token>

JWT authentication token

In: header

Path Parameters

transfer_id*string

Transfer UUID

transfer_id*string

transfer_id path parameter

Header Parameters

Accept-Language?string

Language preference for the response

X-Idempotency-Key?string

Optional idempotency key for finalize retries; when a body is present it must match body idempotency_key. Mismatches return 400 common.invalid_input with details[0].field=idempotency_key and details[0].rule=header_body_mismatch. For requests with a body, omitting both key locations returns 400 common.invalid_input with details[0].field=idempotency_key and details[0].rule=required; empty-body requests may omit both because the stored draft payload is reused. This key governs the draft lookup only. The resulting finalized transfer is deduplicated by a server-derived key bound to the draft transfer_id, so repeated finalize calls on the same draft return the same finalized transfer regardless of the client key sent.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Canonical v2 finalize/draft payment body. Same as v1 CreatePaymentRequest, but client metadata is sent as metadata instead of legacy meta.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v2/transfers/string/finalize" \  -H "Content-Type: application/json" \  -d '{    "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",    "channel": "string",    "amount": {      "amount_minor": "string",      "currency": "string"    },    "source": {      "counterparty_id": "fd38dae9-b300-4017-a630-101c4279eafd",      "cp_account_id": "eb064395-4075-439f-a7b1-d660c0883425"    },    "destination": {      "counterparty_id": "fd38dae9-b300-4017-a630-101c4279eafd",      "cp_account_id": "eb064395-4075-439f-a7b1-d660c0883425"    },    "fee_bearer": "DEBT"  }'
{
  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  "transfer_id": "d4a2d8dd-7def-4545-a062-761683b9aa05",
  "status": "draft",
  "active": true,
  "product_id": "0d012afa-f885-4e65-aeca-37e27701e2d1",
  "product_code": "string",
  "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
  "direction": "outbound",
  "source": {
    "counterparty_id": "fd38dae9-b300-4017-a630-101c4279eafd",
    "cp_account_id": "eb064395-4075-439f-a7b1-d660c0883425"
  },
  "destination": {
    "counterparty_id": "fd38dae9-b300-4017-a630-101c4279eafd",
    "cp_account_id": "eb064395-4075-439f-a7b1-d660c0883425"
  },
  "sender": {
    "name": "Muster Handels GmbH",
    "iban": "CH9300762011623852957",
    "bic": "UBSWCHZH80A",
    "wallet_address": "0x0000000000000000000000000000000000000001",
    "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65",
    "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e"
  },
  "beneficiary": {
    "name": "Muster Handels GmbH",
    "iban": "CH9300762011623852957",
    "bic": "UBSWCHZH80A",
    "wallet_address": "0x0000000000000000000000000000000000000001",
    "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65",
    "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e"
  },
  "channel": "string",
  "fee_bearer": "DEBT",
  "quote_id": "3c071a1d-db86-46a7-9dc8-72ba3fbca992",
  "reference": "string",
  "amount": {
    "amount": "string",
    "currency": "string",
    "precision": 0
  },
  "fee_amount": {
    "amount": "string",
    "currency": "string",
    "precision": 0
  },
  "amount_net": {
    "amount": "string",
    "currency": "string",
    "precision": 0
  },
  "recipient_amount": {
    "amount": "string",
    "currency": "string",
    "precision": 0
  },
  "metadata": {},
  "created_at": "2019-08-24T14:15:22Z",
  "updated_at": "2019-08-24T14:15:22Z"
}
{
  "status": 0,
  "message": "string",
  "code": "transfers_m.account_not_found",
  "class": "validation",
  "retryable": false,
  "details": [
    {
      "field": "amount",
      "rule": "gt",
      "param": "0",
      "message": "amount must be greater than 0"
    }
  ]
}
{
  "status": 0,
  "message": "string",
  "code": "transfers_m.account_not_found",
  "class": "validation",
  "retryable": false,
  "details": [
    {
      "field": "amount",
      "rule": "gt",
      "param": "0",
      "message": "amount must be greater than 0"
    }
  ]
}
{
  "status": 0,
  "message": "string",
  "code": "transfers_m.account_not_found",
  "class": "validation",
  "retryable": false,
  "details": [
    {
      "field": "amount",
      "rule": "gt",
      "param": "0",
      "message": "amount must be greater than 0"
    }
  ]
}
{
  "status": 0,
  "message": "string",
  "code": "transfers_m.account_not_found",
  "class": "validation",
  "retryable": false,
  "details": [
    {
      "field": "amount",
      "rule": "gt",
      "param": "0",
      "message": "amount must be greater than 0"
    }
  ]
}
{
  "status": 0,
  "message": "string",
  "code": "transfers_m.account_not_found",
  "class": "validation",
  "retryable": false,
  "details": [
    {
      "field": "amount",
      "rule": "gt",
      "param": "0",
      "message": "amount must be greater than 0"
    }
  ]
}
{
  "status": 0,
  "message": "string",
  "code": "transfers_m.account_not_found",
  "class": "validation",
  "retryable": false,
  "details": [
    {
      "field": "amount",
      "rule": "gt",
      "param": "0",
      "message": "amount must be greater than 0"
    }
  ]
}
{
  "status": 422,
  "message": "This payee's account cannot accept payments at the moment. Choose another payee, or ask the payee for up-to-date details.",
  "code": "transfers_m.beneficiary_account_not_active",
  "class": "validation"
}
{
  "status": 429,
  "message": "Rate limit exceeded",
  "code": "rate_limits_m.exceeded",
  "class": "temporary",
  "retryable": true
}
{
  "status": 0,
  "message": "string",
  "code": "transfers_m.account_not_found",
  "class": "validation",
  "retryable": false,
  "details": [
    {
      "field": "amount",
      "rule": "gt",
      "param": "0",
      "message": "amount must be greater than 0"
    }
  ]
}

{
  "overall_status": "unhealthy",
  "message": "Service is shutting down",
  "timestamp": "2026-08-27T15:04:05Z"
}

POSTCreate draft transfer from pre-flight context

Creates a transfer in `draft` status from the modern v2 finalize payload. The request body is `DraftPaymentRequestV2` (the draft-relaxed variant of `CreatePaymentRequestV2`) and uses `metadata` for client metadata. Only `source` (and its owning `customer_id`) is required to save a draft; every other field is optional. The server resolves the source (and the destination when supplied) and stores the finalize-shaped payload on the draft; contract validation (product, destination, amount, FX/quote, product pre-flight) is deferred to finalize. First successful saves return 201. A retry with the same key for the same `customer_id` while the stored row is still `draft` and not claimed by finalize returns the existing draft with 200 and `Idempotency-Replayed: true`; the request body is not stored again. Replay grants are re-derived from the saved draft payload when it still resolves: the originator grant stays pinned to the saved `customer_id`, and a beneficiary grant is retried when the saved destination resolves to an internal beneficiary. If the saved destination can no longer be resolved after the draft exists, replay may fall back to persisted beneficiary columns only when they still match an internal account, IBAN, or wallet. Derived participant customer columns are not used by themselves to widen access on retry. Reusing the key after the stored row has left `draft` returns 409 (`transfers_m.draft_idempotency_key_reused`); replaying while finalize has already claimed the draft returns 409 (`transfers_m.draft_finalize_in_progress`). If the same key already belongs to another customer's draft-create row, create returns 409 (`transfers_m.idempotency_key_conflict`) instead of returning that row. Use `PUT /v2/transfers/{transfer_id}` to edit the draft and `POST /v2/transfers/{transfer_id}/finalize` to execute it.

POSTFinalize transfer from pre-flight context

Canonical modern finalize endpoint. Creates a transfer after pre-flight discovery and returns `TransferPaymentResponse`. Request body is `CreatePaymentRequestV2`: same selector/amount semantics as v1 finalize, but client metadata is sent as `metadata` (not `meta`). The server maps to the same product matrix filter, resolves `source` to the real debit `source_account_id = accounts.accounts.id`, normalizes `destination` to exact universal `destination_account_id = counterparties.cp_accounts.id`, runs `ValidatePreFlightRequest`, then `PreFlightForFinalize`; on deny returns 422. Cross-currency requires a locked top-level `quote_id` from `POST /v2/transfers/quotes`.