POST to your URL when an order, payment account, KYC or KYB status changes. Order and payment account events include the full object.
Webhooks vs. polling. Webhooks arrive when the state changes. Polling is slower and most requests return no change. Use webhooks for anything time-sensitive, and read the object with its
GET endpoint when you need to confirm the current state.Subscribe
Create one subscription per URL and event withPOST /webhook_subscription:
The response is
201 and includes a secret:
- In production the URL must use
https. Sandbox also acceptshttp. - A second subscription for the same URL and event returns
409conflict. GET /webhook_subscriptionslists your subscriptions.GET /webhook_subscription/{id}reads one.DELETE /webhook_subscription/{id}removes one.- Subscriptions belong to your organization and do not take an organization id in the route. One subscription covers your organization and every customer organization, so you do not subscribe per customer. Each payload tells you which organization the event is for.
Payload
orderUpdated and paymentAccountUpdated share one envelope:
When each event is sent
orderUpdated
- When the order’s
statuschanges, for example tofunded,completed,failedorcanceled. - When the order’s funding instructions arrive or are replaced.
order.fundingInstructions.destinationthen holds where to send the funds.
POST /organization/{organizationId}/order response already holds the order.
What your application does on each status:
paymentAccountUpdated
Sent when the account’sstatus changes, or when the status of any rail direction in rails changes (for example destination on spei going from pending to ready). Quote a direction only once it is ready.
kycUpdated
Sent when a customer’s KYC status changes, and when a write you sent was accepted but could not be applied.kyc.status is one of notStarted, inProgress, submitted, approved or denied. Do not let a customer transact until kyc.approved is true. When kyc.needsWork is true, kyc.userMessage says what the customer has to fix.
To learn what is still outstanding, read GET /organization/{organizationId}/kyc?requirements=true when the webhook arrives. Do not poll it on a timer. A kycUpdated that moves the customer from submitted back to inProgress means the next stage has arrived and the list holds new requirements. See Tracking verification status and Requirements unlock in stages.
kybUpdated
Sent when a business customer’s KYB status changes.kyb.status uses the same values as KYC, and kyb.approvedAt is set once the business is approved.
Verify the signature
Each delivery has anX-Signature: sha256={hex} header. We compute it as HMAC-SHA256 over the RFC 8785 canonicalized JSON body. The HMAC key is your subscription’s secret, decoded from URL-safe base64.
The secret uses the URL-safe base64 alphabet (RFC 4648 section 5:
- and _ in place of + and /), with = padding. It decodes to 32 bytes. Decode it with a URL-safe decoder, for example base64.urlsafe_b64decode in Python or Buffer.from(secret, "base64url") in Node.js. A standard base64 decoder fails or returns the wrong key for secrets that contain - or _.Delivery and retries
- Return any
2xxto acknowledge a delivery. - We make up to 3 attempts per delivery, 5 seconds apart. We try again after a connection error, a timeout, a
5xx, a408, a429, or any other non-2xxstatus not in the next item. - We stop after the first response when your endpoint returns
400,401,403,404,405,410or422. - We do not send an abandoned delivery later. Read the object with its
GETendpoint to recover the current state.
Best practices
- Queue events before processing. Save the payload to a queue and return
2xxpromptly to avoid timeouts and retries. - Ignore duplicate events. Use
eventIdto avoid processing a retried delivery twice. - Check event timestamps. Deliveries can arrive out of order. Compare
occurredAtbefore updating stored state; use the resource’sGETendpoint to confirm its current state. - Expose a public HTTPS endpoint. For local development, use a tunnel such as ngrok so we can reach your handler.
Migrating from v1
v1 and v2 subscriptions are separate. A v1 subscription keeps receiving the v1 payload and does not appear inGET /webhook_subscriptions. To receive the v2 payloads, create orderUpdated, paymentAccountUpdated, kycUpdated and kybUpdated subscriptions. See the V1 → V2 guide.