Skip to main content
Not every delivery at your stores was created by you. A store whose owner can sign in to the merchant console can dispatch an order from there by hand, and a store that takes orders from its customers on WhatsApp has the delivery for each one created when it accepts the order. Either delivery belongs to your account like any other: it is in your list and it fires your webhooks. 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 with origin: "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_changed and delivery.updated, whose data is 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.
How to be told about any of this without polling is webhooks.