Skip to main content

Receive Custodial Webhook

Inbound endpoint a custody provider calls to report the progress of a transaction the platform submitted to it. You only need this page if you operate the custody service the platform is connected to; partner integrations do not call it.

It is authenticated per provider with the provider's webhook secret (generated in the admin panel's custody settings and shown once), not with an OAuth access token. A provider whose webhook is configured to require a client certificate must also present the certificate issued to it in the TLS handshake (see mTLS Client Certificates; the subject is the provider key).

Request​

POST /v2/webhooks/custodial

POST /v2/webhooks is a deprecated alias with the same behaviour, kept only for senders still configured with it. Do not configure a new sender with it.

Headers​

NameDescription
AuthorizationRequired (or X-Webhook-Token). Depending on the provider's auth scheme: Basic <base64(username:webhook_secret)>, or Bearer <webhook_secret>
X-Webhook-TokenAlternative to Authorization: Bearer for senders that cannot set Authorization. Carries the webhook secret
X-Apex-Delivery-IdRecommended. The sender's id for this delivery. The platform logs the delivery under it, so the same delivery received twice is one log entry
Content-TypeRequired. application/json

Body​

FieldTypeRequiredDescription
idstringYesThe custody transaction id: the id the platform submitted the transaction under
custody_transaction_idstringNoThe custody service's own row id. Logged only
external_idstringNoOptional external reference
typestringYesCustody operation type, for example execute_contract
statusstringYespending_approval, queued, signing, broadcast, confirmed, rejected, failed, cancelled or expired
transaction_hashstringNoOn-chain transaction hash, once broadcast
contract_addressstringNoContract the transaction created or called
gas_usednumberNoGas used, once the receipt is available
effective_gas_price_weistringNoDecimal string. Logged only
gas_fee_weistringNoDecimal string. Logged only
gas_fee_nativestringNoDecimal string. Logged only
errorstring | nullNoFailure reason for rejected / failed / cancelled / expired
created_atstringYesISO 8601
updated_atstringYesISO 8601

Unknown fields are ignored.

Example​

curl --request POST \
--url '{{baseUrl}}/v2/webhooks/custodial' \
--header 'Authorization: Bearer <webhook_secret>' \
--header 'X-Apex-Delivery-Id: 3b1f6c1e-8c55-4d47-9a0e-4b1c2f0c9a11' \
--header 'Content-Type: application/json' \
--data '{
"id": "6913872755654191dede37da",
"type": "execute_contract",
"status": "confirmed",
"transaction_hash": "0xc34e006659d532a0c8212a8ec624a08cfab3ffbd102936a681ab81e3cf7e0c6d",
"contract_address": "0x39247A80f1c0ddcEfb4986fA7A59E919E1F2Fa38",
"gas_used": 142037,
"error": null,
"created_at": "2026-02-06T13:04:58.802Z",
"updated_at": "2026-02-06T13:05:09.911Z"
}'

Response​

200 with an empty body once the delivery is accepted.

How a delivery is processed​

  • Final statuses are read back. For confirmed, rejected, failed, cancelled and expired the platform does not trust the body: it reads the transaction from the custody service and publishes custody's answer (status, hash, contract address, gas, error). The webhook secret proves who sent the delivery, not what happened.
  • If that read fails or finds nothing, the delivery is refused with 503 (code 20009) so the sender retries. The platform's own polling still settles the transaction, so a verdict is delayed, never lost.
  • Intermediate statuses (pending_approval, queued, signing, broadcast) are passed on as delivered.
  • A transaction that already reached done or error on the platform keeps its first verdict; a later delivery for it does not change it.

Errors​

StatusCodeDescription
400-1The body does not parse or a field has the wrong type
401-1The credentials match no provider's webhook secret, or the provider requires a certificate and the handshake did not present the one issued to it
50320009The final status could not be read back from the custody service. Retry the delivery

See Errors for the error format.