DocsGuides

Kitchen POS connector

Receive each new Coastly order in a restaurant's POS, and report it accepted, ready or cancelled.

Restaurants on Coastly run their kitchen from the POS they already use. When a customer places an order, Coastly pushes it to the restaurant's POS as a signed JSON request, so it prints or shows on the kitchen screen like any other ticket. The POS then tells Coastly when the kitchen accepts the order, when it is ready, or if it has to be cancelled.

This guide is for POS companies building that connection. There is nothing to install: you give Coastly an https endpoint, and Coastly gives you a signing secret and a callback URL for that restaurant.

Getting connected

  1. Send the Coastly team the https URL that should receive orders for the restaurant (one URL per restaurant location).
  2. Coastly sets up the connection and sends you two things for that restaurant:
    • a signing secret (whsec_…), shown once. Store it like a password.
    • a status callback URL, https://coastly.surf/api/pos/{connection_id}/status.
  3. Coastly sends a pos.test push to your endpoint. Answer it with any 2xx.

The same secret signs both directions: Coastly signs every push to you, and you sign every status callback to Coastly.

Orders: Coastly to your POS

Coastly POSTs one request per new order, as soon as the restaurant can see the order:

POST https://your-endpoint.example.com/coastly
Content-Type: application/json
X-Coastly-Signature: t=1790791200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-Coastly-Event: pos.order.created
X-Coastly-Event-Id: 8f14e45f-ceea-4e67-a9c4-1f2d3c4b5a69
{
  "id": "8f14e45f-ceea-4e67-a9c4-1f2d3c4b5a69",
  "type": "pos.order.created",
  "created": 1790791200,
  "api_version": "v1",
  "data": {
    "object": {
      "id": "0b6c1d2e-3f40-4a51-8b62-7c83d94ea5f6",
      "ref": "#4EA5F6",
      "vendor_id": "1c7d2e3f-4051-4b62-9c73-8d94ea5fb607",
      "placed_at": "2026-09-30T18:00:00.000Z",
      "fulfillment": "delivery",
      "customer": { "first_name": "Ana" },
      "items": [
        { "name": "Fish tacos (no onions)", "qty": 2, "unit_price_cents": 900 },
        { "name": "Agua de pipa", "qty": 1, "unit_price_cents": 600 }
      ],
      "notes": null,
      "currency": "USD",
      "totals": { "subtotal_cents": 2400, "delivery_cents": 300, "total_cents": 2700 },
      "payment": {
        "method": "cash",
        "prepaid": false,
        "note": "The customer pays the restaurant directly."
      }
    }
  }
}

Field notes

Field Notes
id (envelope) Unique per push. Use it to ignore a repeat delivery. Also sent as X-Coastly-Event-Id.
data.object.id The Coastly order id. Send it back in status callbacks.
ref The short reference the restaurant, the driver and the customer all see. Print it on the ticket.
fulfillment pickup (the customer collects) or delivery (a driver collects from the kitchen).
customer.first_name First name only, or null.
items[].name The menu item, including any customization the customer typed, for example "(no onions)".
items[].unit_price_cents, totals Integer cents, in currency.
notes An order note for the kitchen, or null.
payment Coastly never takes the order payment. prepaid is always false: the customer pays the restaurant directly, by the method they chose (cash, yappy, card_driver, and so on). Never book a Coastly order as paid in advance.

The push never contains the customer's phone, address or payment details, and never the pickup PIN or delivery code: those are exchanged in person, in the Coastly app.

Answering

Answer with a 2xx within a few seconds, then do any slow work afterwards. Any other status, or no answer within 10 seconds, is retried with backoff (roughly 5 minutes, 30 minutes, 2 hours, 6 hours, then once a day), up to 8 attempts in all. The restaurant's WhatsApp order alert does not depend on the POS, so a POS outage never holds it back.

Coastly sends each order to a restaurant's POS once. A retry of the same push carries the same envelope id.

Status: your POS to Coastly

When the kitchen acts on an order, POST to the restaurant's status callback URL:

POST https://coastly.surf/api/pos/{connection_id}/status
Content-Type: application/json
X-Coastly-Signature: t=1790791500,v1=…
{ "order_id": "0b6c1d2e-3f40-4a51-8b62-7c83d94ea5f6", "status": "accepted" }
status What happens on Coastly
accepted The kitchen has the order. The customer is told it was accepted.
ready The food is ready: ready for pickup on a pickup order, ready for the driver on a delivery. Sent on an order that was never accepted, it accepts it first.
cancelled The restaurant cannot make it. Add an optional "reason" (up to 200 characters); the customer is told. Refused once a driver has picked the food up.

Sign the callback exactly as Coastly signs its pushes: v1 is the hex HMAC-SHA256 of ${t}.${rawBody} with the restaurant's signing secret, where t is the current Unix time in seconds. Coastly refuses a signature more than 300 seconds old or new.

import crypto from "node:crypto";

const body = JSON.stringify({ order_id, status: "accepted" });
const t = Math.floor(Date.now() / 1000);
const v1 = crypto.createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");

await fetch(callbackUrl, {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-Coastly-Signature": `t=${t},v1=${v1}` },
  body,
});

Responses

Status Meaning
200 Done. The body is { "ok": true, "order_id": "…", "status": "accepted" } with the order's status now. Sending the same status again, or one the order has already passed (an accepted after it is ready), changes nothing and answers 200.
401 Missing, wrong or stale signature. Nothing changed.
404 Unknown or paused connection, or an order that is not this restaurant's.
409 The order cannot move that way from where it is (for example, it was already cancelled). The message says why.
422 The body is not valid JSON, or order_id / status is missing or not one of the values above.
429 Too many requests. Retry after a minute.

Errors use the standard Coastly error body, { "error": { "code": "…", "message": "…", "request_id": "…" } }.

Verifying Coastly's pushes

Verify every push before trusting it, exactly as described in webhooks: read the raw body, recompute the HMAC with the restaurant's signing secret, compare in constant time, and reject a timestamp more than 300 seconds away from now.