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.
The token in the headers is what identifies a delivery as genuine, so two rules apply:
- Serve the endpoint over HTTPS only, so the token is never sent in cleartext.
- 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.
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.
processingcan land afterdonefor 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
| Entity | Delivers on |
|---|---|
deposit | every change to the record, including the non-terminal aml_processing and processing |
withdrawal | processing, done, fail, postponed, rejected, aml_rejected, expired, cancel |
order | every state except wait — so done, cancel and fail |
trade | each fill |
market | a 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.