Freeport Seller API

Webhooks

An endpoint is an HTTPS URL of yours and a list of event types, or an empty list for everything. Up to five per account. Each has its own secret, shown once when it is created and again only when rotated.

The delivery

A POST with a JSON body: { "id", "type", "created_at", "api_version", "data": { … } }. For an order event data.order is the order exactly as GET /v1/orders/{id} returns it; for a listing event data.listing likewise; for order.message, the message. Four headers ride along:

HeaderMeaning
Freeport-Signaturet=<unix seconds>,v1=<hex>. Two v1 values for a day after a secret rotation.
Freeport-Event-IdThe event's id. The same event never arrives under another id, so this is the key to deduplicate on.
Freeport-Event-TypeThe type, so a router need not parse the body.
Freeport-Delivery-IdThis attempt, for the log in the console.

Verifying

Compute HMAC-SHA256 with the endpoint's secret over ${t}.${body}, where t is the timestamp from the header and body is the raw bytes exactly as received, not re-serialised. Accept the delivery if any v1 value equals your result, compared in constant time. Refuse a t more than five minutes from now. Then answer any 2xx quickly and do the work afterwards: a delivery waits ten seconds for an answer and never follows a redirect.

Retries

A non-2xx answer or a timeout is retried after one minute, five, thirty, two hours, six, twelve, then daily, ten attempts in all. An endpoint that has accepted nothing for three days is disabled, with an email to the account and a row in the audit log; re-enable it from the console once it is fixed, and replay what it missed from the delivery log. Order events carry seq: ordering across events is not guaranteed, so fetch the order on receipt rather than trusting the body's state to be the latest.

Events

TypeWhen
order.paidMoney is held; the accept clock is running. The one to act on.
order.accepted, order.delivering, order.delivered, order.completedThe order moving through its states, including moves your own tool made.
order.cancelled, order.refunded, order.expiredMoney went back. Stock is restored.
order.disputed, order.dispute_resolvedA case opened and decided; the outcome is in the payload.
order.messageThe buyer or the system wrote in the thread. Never your own words.
listing.low_stock, listing.sold_outA sale crossed your alert quantity, or took the last unit.
listing.taken_down, listing.reinstatedAn operator removed a listing, or put it back.
account.trading_status_changedYour account was suspended or reinstated.
pingThe test button.

Where they come from

Every event is written in the same database transaction as the change it describes and dispatched from there. Nothing is emitted from a request handler, so an event never describes a change that rolled back, and a change never happens without its event. That is also why an event can arrive a second or two after the state it names: it is real by then.