| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
Generic Picking
Generic Picking lets a third-party picking partner play the same role Deliverect's own
Quest picker plays: it receives retail orders to pick, reports picking progress, requests
substitutes, and finalises the order. It is a push + callback integration, not a
POS↔channel bridge.
TL;DR — A retail channel (Deliveroo, Uber Eats, …) sends an order to Deliverect.
Deliverect pushes a filtered copy of that order to the partner'sorderWebhookUrl. The
partner then calls back into Deliverect's/picking/order/...endpoints (OAuthgenericPicking
scope) to drive the picking lifecycle. The partner never creates or fetches orders.
Roles (read this first)
| Term | What it is |
|---|---|
| Channel | The marketplace/storefront where the consumer ordered (Deliveroo, Uber Eats, Just Eat/Flyt, Glovo, Wolt, DiDi Food, Delivery Hero, DoorDash, PIK, or a Generic retail channel). Orders originate here. |
| Deliverect | The middleware. Ingests the channel order, creates a retail order, routes it to a picker. |
| Picker | The app doing the physical picking. Quest is Deliverect's own picker; Generic Picking is the same role exposed to an external partner. |
| POS | The store's point-of-sale/inventory system. A separate integration (genericPOS scope). A partner may be both a POS and a picker, but the roles/scopes are distinct — the picking API is not a POS feature. |
End-to-end flow
%%{init: {'theme': 'base', 'themeVariables': {'background': '#ffffff', 'mainBkg': '#ffffff', 'fontSize': '24px'}}}%%
sequenceDiagram
participant C as Retail Channel
participant D as Deliverect
participant P as Picking Partner
C->>D: New retail order
D->>P: POST {orderWebhookUrl}/picking/order (filtered order, HMAC)
P->>D: POST /picking/order/{id}/start
P->>D: POST /picking/order/{id}/item/{itemId}/suggestSubstitute
D-->>P: 200 [substitute candidates] (synchronous — pulled, not pushed)
P->>D: POST /picking/order/{id}/item/{itemId}/pick
P->>D: POST /picking/order/{id}/updateOrderItems (ADJUST / REPLACE / REMOVE / PICK)
Note over D: amendments/substitutions forwarded to the CHANNEL
P->>D: POST /picking/order/{id}/done
D->>P: POST {orderWebhookUrl}/picking/order/{id}/update (lifecycle events, e.g. CUSTOMER_APPROVAL_SUBSTITUTION)
Two directions, don't conflate them:
- Outbound (Deliverect → partner) — controlled by POS settings. New order + every lifecycle
event are POSTed to the singleorderWebhookUrlbase. - Inbound (partner → Deliverect) — the
/picking/order/...endpoints, gated by the OAuth
genericPickingscope (+ HMAC). Not controlled by POS settings.
Configuration (POS settings)
Only two settings are wired. Set them on the location — Deliverect propagates location POS
settings to that location's channel links, so every channel at the location routes to the same picker.
| Setting | Wired? | Meaning |
|---|---|---|
isGenericPickingActive | ✅ | Route this link's orders to the generic partner instead of Quest. |
orderWebhookUrl | ✅ | Partner base URL. Deliverect appends /picking/order (new order) and the lifecycle sub-paths. Give the base only — no trailing slash, no /picking/order. |
The order push is fire-and-forget with no retry (
_sendToGenericPickinglogs and swallows
errors). If the partner endpoint is down/slow (10s timeout) the order silently doesn't arrive and
Deliverect does not error — check logs for[GenericPicking].
Inbound endpoints (partner → Deliverect)
Base: https://api.deliverect.com · Auth: OAuth2 client-credentials, scope value genericPicking
(the camelCase string — GENERIC_PICKING is only the internal enum member name) · POSTs are additionally
HMAC-signed (X-Deliverect-Hmac-Sha256).
| # | Method & path | Purpose |
|---|---|---|
| 1 | POST /picking/order/{id}/start | Start picking |
| 2 | POST /picking/order/{id}/item/{itemId}/pick | Mark an item picked |
| 3 | POST /picking/order/{id}/item/{itemId}/suggestSubstitute | Get substitute candidates (synchronous response) |
| 4 | POST /picking/order/{id}/updateOrderItems | Amend items (ADJUST / REPLACE / REMOVE / PICK) |
| 5 | POST /picking/order/{id}/reject | Reject the order |
| 6 | POST /picking/order/{id}/done | Finish picking |
| 7 | POST /picking/order/{id}/couriers | Update required courier count |
There is intentionally no POST /picking/order (create) and no GET /picking/order/{id}
(fetch) — orders are pushed, not created or pulled by the partner, matching Quest's contract. A pull
endpoint can be added later if a genuine use case appears (e.g. reconciliation).
This inbound set mirrors Quest's own callbacks (routes/retail/routes.py) and shares the same
processor functions (Middleware/RetailButler/pickerRoutesUtils.py), so behaviour is identical to
Quest — the only difference is the partner receives a filtered order payload
(filterOrdersGenericPicking).
Substitutes: pulled, not pushed
- The partner pulls candidates by calling
suggestSubstitute; Deliverect returns them
synchronously in the HTTP response. Depending on the item's unavailable-action, candidates come
from the channel (_getCandidatesFromChannelAPI) or from Deliverect's catalog
(_getCandidatesFromCatalog, only for channels withsupportsSubstitutionStrategy— currently
Uber Eats and Flyt/Just Eat). - Applying a substitution/amendment (
updateOrderItemsREPLACE) is forwarded to the channel
via the channel settingsretailSubstitutionsEndpointURL/retailAmendmentsWebhookURL
(that's theChannels/Genericlayer, not a picker setting). - Substitution results reach the partner via the outbound
updatelifecycle event
(eventType=CUSTOMER_APPROVAL_SUBSTITUTION) toorderWebhookUrl. There is no separate
"substitutes webhook".
HMAC
Outbound pushes are signed: X-Deliverect-Hmac-Sha256 = HMAC_SHA256(key=secret, msg=<raw JSON body>)
(hex). The secret is the configured GENERIC_PICKING extension key; when it isn't configured
(typical local/staging) it falls back to the location id, which is present as the location
field in the payload — so a receiver can self-verify with that value.
Notes / gotchas
- Config scope: set the active flag and URL on the location. Deliverect propagates location POS
settings to the location's channel links (ChannelButleron link creation +
updateAndPropagateLocationPOSSettings), so both scopes resolve to the same values and every channel
at the location routes to the one picker. - No retry on the outbound push (see above).
- Historically there were three extra POS settings (
genericPickingApiKey,orderUpdatesUrl,
getSubstitutesUrl) that were never consumed; they have been removed. Access is controlled by the
OAuth scope, updates and substitutes both use the mechanisms described above.
200