Send orders in from your own system.
A website, a kiosk, a register that isn't on Square yet, a spreadsheet with a Zapier automation — anything that can make an HTTP request can put an order on your board. Send a name or a ticket number and it appears instantly; your staff pick it up, mark it ready, and hand it off exactly like any other order.
Get your key
From your dashboard, open a shop and find Custom integration. Click Generate key — your
key (it starts with psk_) is shown exactly once, so copy it somewhere safe right away, like a
password manager. There's no way to view it again later, only replace it: generating a new key immediately
stops the old one from working. You can also revoke a key without generating a new one, from that same panel.
The endpoint
Every order is one authenticated POST request.
POST https://pickupsignal.com/api/ingest-orderAuthorization: Bearer psk_your_key_hereContent-Type: application/jsonFull example
A real order, ready to copy and run — swap in your own key and shop id.
curl -X POST https://pickupsignal.com/api/ingest-order \
-H "Authorization: Bearer psk_DeiyfU6z9m0nmhpexfPet7lLVdT2wVhpwoFirNkhIoc" \
-H "Content-Type: application/json" \
-d '{
"shopId": "Kx3Yq9dLmPa2Rv8Tw4Zn",
"name": "Priya",
"drink": "Taro Milk Tea",
"source": "online",
"id": "order-10432"
}'Response — 200
{"num": 47, "orderId": "i2a91cf6b8d4e4a2b9f7d3c1a5e6b8f90"}Running a
ticket-number board instead of names? Send ticket in place of name:
{"shopId": "Kx3Yq9dLmPa2Rv8Tw4Zn", "ticket": 47, "id": "order-10433"}.
Fields
Every request body needs a shopId and at least one of name or
ticket. Everything else is optional.
| Field | Type | Required | Constraints |
|---|---|---|---|
shopId | string | Yes | Your shop's id — shown in the Custom integration panel alongside your key, with its own Copy button. |
name | string | Name or ticket | Up to 120 characters. Shown on the board as the order's name; extra whitespace is collapsed. |
ticket | integer | Name or ticket | 0–999999. Used for a numbered-ticket board instead of a name. Send both and the ticket number wins for numbering — the name is still kept on the order. |
drink | string | No | Up to 120 characters. Shown alongside the name or ticket on the board. |
id | string | No, recommended | Up to 128 characters. Your own unique reference for this order — makes the request safely repeatable. See Retries & idempotency below. |
source | string | No | One of instore, online, app. Defaults to
online when omitted. |
Responses & errors
A created order returns its board number and id. Every rejection returns a short
error code you can branch on.
| Status | error | Meaning |
|---|---|---|
| 200 | — | Created. Body: {"num", "orderId"}. |
| 200 | — | Duplicate replay of the same id — see below. Body:
{"duplicate": true, "num", "orderId"}, matching the original order. |
| 400 | name or ticket required | Neither name nor
ticket was sent. |
| 400 | ticket out of range | ticket wasn't a whole
number from 0 to 999999. |
| 400 | invalid source | source wasn't
instore, online, or app. |
| 400 | invalid id | id was present but empty, not a
string, or over 128 characters. |
| 400 | shop-timezone-unset | The shop has no timezone on file and
the request had no ticket — see below. |
| 401 | unauthorized | Missing or wrong Authorization
header, wrong shopId for that key, or a revoked key. |
| 404 | shop-not-found | The shop no longer exists. |
| 405 | method-not-allowed | Something other than
POST. |
| 429 | rate limited | Over 60 requests/minute for this shop. A
Retry-After: 30 header says how long to wait. |
| 500 | service-unavailable | Retry later; if it persists, regenerate your key or contact support. |
ticket with every request sidesteps it entirely.Retries & idempotency
Include id on every request and you can never create the same order twice: send the same
id again on the same business day and you'll get back the original order
(duplicate: true, the same num and orderId) instead of a new one.
That scoping is per day, not forever — reuse order-42 again tomorrow for a different order
and it's treated as new.
id — map a stable, unique field from your trigger (an order number, a row id,
anything that identifies that specific order) into id, and a retried delivery lands as the
same order instead of a duplicate on your board.A Zapier or Make recipe, no code needed
The same five steps whether you're wiring up Zapier's Webhooks action or Make's HTTP module.
- Trigger. Whatever starts a new order in your existing tool — a new row in a spreadsheet, a new order in your online store, a new form submission.
- Action. Add Webhooks by Zapier (Zapier) or an HTTP module (Make), set to
POST, URLhttps://pickupsignal.com/api/ingest-order. - Headers.
Authorization: Bearer psk_your_key_hereandContent-Type: application/json. - Field mapping. Build the JSON body from your trigger's fields —
shopId,name(orticket), optionallydrink, andidmapped to something unique from the trigger so retries stay safe. - Test safely. Run it once by hand with a fixed body like
{"shopId": "your-shop-id", "name": "Test Order", "id": "test-1"}. Watch it land on your board, then open the staff page and tap Remove from queue on that order before turning the automation on for real — it asks you to confirm, then gives you 6 seconds to Undo if you tap it by mistake.
Rate limit
60 requests per minute, per shop. Go over it and you'll get a 429 with a
Retry-After: 30 header — wait 30 seconds and try again. Ordinary order volume, even a busy
counter, comes nowhere close; the limit exists to catch a runaway integration, not to slow down real
traffic.
Orders appear on your board instantly — staff advance them like any other order.