origin is the field that says which of the three happened.
Those three values are the whole set. Every delivery carries one, it is always a
string and never
null, and it never changes: a delivery is created once, by
one of the three, so the answer is fixed the moment the delivery exists. A
no_courier delivery you retry keeps it, along with its id and your
external_id.
A console or WhatsApp order is not a bug in your integration
A console order reaches you withorigin: "console" and external_id: null,
and a WhatsApp order with origin: "whatsapp" and external_id: null. Neither
will match anything in your database, because you never put it there. That is
what these orders look like, not a sign that something went wrong.
Every platform where a person and a program create the same kind of record works
this way. A charge somebody enters by hand in a dashboard still reaches the
webhooks of the system integrated with it, for the same reason: the record is
real, the account is yours, and being told about only half of what happened on
it would leave your books short.
So render them or ignore them, whichever suits what you are building. What you
should not do is treat one as a failure: no alert, no dead letter queue, and no
error status back to a signed event you understood perfectly well.
One habit to break first. Do not tell your orders from the others by looking for
a missing external_id. It happens to line up, because the API requires one on
every create and neither the console nor a WhatsApp order has a field for it, but
it is a guess assembled from another field, and origin is the field that
answers the question.
Whose money a console or WhatsApp order spends
A store can dispatch from the console only when it owns its money, so a console order always spends that store’s own balance and never yours. Your share of the fee is debited there when the delivery is created and cash the courier collects is credited there when it arrives, exactly as they are for an order you created through the API at the same store. A WhatsApp order is billed by the same rule as every other delivery at its store, so at a store that owns its money it spends that store’s balance and not yours, with the same debit when the delivery is created and the same credit for cash when it arrives. Where a store’s money lives and who may sign in are one setting,access_tier,
and the money model has the table. Only a full
store can produce a console order: at api_only nobody can sign in at all, and
at wallet the owner reaches the balance pages rather than dispatch. A WhatsApp
order has the same limit one step earlier: a store takes orders on WhatsApp only
after its owner connects a number in the console’s assistant settings, and only
a full store can open them. So a delivery created while a store is at either
of those tiers has origin "api". A store’s tier can change and a delivery
keeps the origin it was created with, so read origin off the delivery and
never infer it from the store’s current tier.
Accepting does not need a person in the console. A store can have its WhatsApp
orders accepted automatically, so a whatsapp delivery can appear while nobody
is signed in.
That is worth knowing in both directions. If no store of yours is on full
today, you will never see a console or WhatsApp order, and handling for them
costs nothing to leave in against the day one of them is.
Asking for only your own orders
GET /v1/deliveries?origin=api lists only the deliveries you created,
origin=console lists only those the stores dispatched themselves, and
origin=whatsapp lists only those their customers ordered on WhatsApp. It
combines with the other filters, so “mine, at this store, still on the road” is
one request rather than a walk through every page discarding rows as they arrive.
GET /v1/deliveries has the full
set of filters.
The webhook stream has no equivalent. Every delivery at your stores fires
events, by design, so a subscription cannot be narrowed to one origin and the
filtering happens in your handler instead. Read origin off data and answer
2xx either way: the event was delivered correctly, and anything else earns you
six retries of an order you had already decided to ignore.
Where origin appears
- On the delivery object wherever it is returned: the create, a read, the list, a cancellation and a retry.
- On
delivery.status_changedanddelivery.updated, whosedatais that same whole object. - Not on
delivery.courier_location. Its body is the slim position object and carries no delivery fields beyond the ids, so read the delivery back or use what you already stored. - Not on a quote. A quote is a price rather than an order, so it has nothing to have come from yet.