Skip to main content

Receiving Deliveries

Everything a receiving endpoint has to handle: how to tell a genuine delivery from a forged one, which events arrive, and what the delivery contract does not guarantee.

How to trust a delivery

The token in the headers is what identifies a delivery as genuine, so two rules apply:

  1. Serve the endpoint over HTTPS only, so the token is never sent in cleartext.
  2. Re-read the object through the API before anything irreversible. Treat a delivery as a signal that something changed, and the API as the record of what it changed to.
Build the receiver for gaps and for reordering

Delivery is asynchronous, carries no sequence number, and is not re-sent. Two consequences to handle:

  • A delivery may not arrive. If your endpoint is unavailable or answers slowly, that notification is not repeated. Poll the API for anything you need to be certain of.
  • Deliveries may arrive out of order. processing can land after done for the same object.

So make the handler idempotent and apply state by comparing updated_at rather than by arrival order:

on delivery:
existing = lookup(payload.id)
if existing and existing.updated_at >= payload.updated_at:
ignore # this is older news than what we already have
else:
apply(payload)

Writing the handler as "set status = payload.status" instead will eventually walk a customer's payout backwards from done to processing.

One subscription per entity​

A subscription is registered per action, not per status. One withdrawal subscription is enough for every payout notification: processing, done, fail, postponed, rejected, aml_rejected, expired and cancel all arrive on it. You never register per status.

Which statuses fire​

EntityDelivers on
depositevery change to the record, including the non-terminal aml_processing and processing
withdrawalprocessing, done, fail, postponed, rejected, aml_rejected, expired, cancel
orderevery state except wait — so done, cancel and fail
tradeeach fill
marketa change to a market

So a receiver must expect intermediate statuses, not only final ones. A deposit delivery fires on every change to the record rather than on a change of status, so the same status can arrive more than once; and nothing is sent until the deposit is registered in the core, so its earliest status may never arrive. Deduplicate on id, status and updated_at together.

Creation itself is never delivered. There is no event for an order being accepted and none for a payout being accepted, so the response to Create Order and Create Withdrawal is the record that the object exists. For a payout, the first status you can receive by webhook is processing.

So the statuses a payout can be read in and the statuses that fire a delivery are not the same set: aml_processing is a real status on the payout itself — see Statuses — and it never arrives as a delivery.

Authenticating a delivery​

The token you registered is sent on every request, in two headers at once:

Webhook-Token: YOUR_WEBHOOK_TOKEN
Authorization: Bearer YOUR_WEBHOOK_TOKEN

If your endpoint is configured for basic auth, the second header is Authorization: Basic base64(username:token) instead.

Compare the token to the one you registered, on every request, and reject anything that does not match.