Store logo
Registrera
Logga in

Order Partner API

Place phone orders, move the kitchen status, pause a location and receive signed events from a Frontspace restaurant store.

Status: beta · Verified against the code 2026-10-10

For a system that takes food orders for a Frontspace restaurant (by phone, an AI agent, or your own channel), follows them through the kitchen, and is told about every change.

What you can do:

Read the menu Locations, opening hours, pause state, dishes, prices and option groups
Place orders Priced by Frontspace, paid by the customer at pickup
Cancel An unpaid order you placed
Move the kitchen status Accepted → ready → picked up, or rejected
Pause and resume orders When the kitchen is full, from your system
Be told A signed webhook for every order event, plus a feed to catch up from

Every endpoint, scope, event and error code in this guide is checked against our code on every change. If the API does something this guide does not say, tell us: it is a bug.


1. Getting access

You integrate as an app installed on each restaurant's store. Each installation has its own access token, which only ever sees that one store.

  1. We register your app. We set it up with the scopes in the table below and your URLs. You get an api_secret, which you use to verify install callbacks (§1.2). Store it as a secret on your side.
  2. We install it on the Frontspace Teststore. This is your sandbox: a real store with a real menu, kitchen screen and till, where nothing reaches a real customer. Build and test everything here.
  3. The restaurant installs it. Installing is the restaurant's consent: it can uninstall at any time, which revokes your token at once.

1.1 Scopes

Scope Lets you
read_orders Read orders and receive order events, without customer contact details
read_customers Adds customer name, email, phone and address to orders and events
read_food_menu Read locations, hours, pause state and the orderable menu
create_orders Place phone orders and cancel the ones you placed
write_merchant_managed_fulfillment_orders Move the kitchen status of the restaurant's orders
write_food_locations Pause and resume orders for a location, and set its preparation time

The restaurant can remove a scope at any time. Scopes are checked again when each webhook is sent, so removing one also stops the data on webhooks you are already subscribed to.

1.2 Receiving your access token

When a store installs your app, its token (fsapp_…) can reach you in two ways:

  • Install callback (recommended). If you give us an install_callback_url, we POST the token to it, signed. Nobody has to copy or paste a credential.
  • Shown once in the admin. Whoever clicks Install sees the token once, and hands it over to you.

The callback body:

{
  "event": "installed",
  "app_id": "<uuid>",
  "store_id": "<uuid>",
  "access_token": "fsapp_…",
  "granted_scopes": ["read_orders", "read_customers", "read_food_menu", "create_orders", "write_merchant_managed_fulfillment_orders", "write_food_locations"],
  "timestamp": "2026-10-10T10:00:00.000Z"
}

event is installed, uninstalled (no token; stop calling for that store) or settings_updated.

Verify every callback before trusting it:

X-Frontspace-Timestamp: <unix seconds>
X-Frontspace-Signature: hex( HMAC-SHA256( api_secret, "<timestamp>.<raw body>" ) )

Reject a timestamp more than 5 minutes from your clock, and compare signatures in constant time.

⚠️ The callback is sent once, with a 10-second limit, and is not retried. If your endpoint is down when a store installs, that token is lost: tell us, and we reinstall to issue a new one. Store the token keyed by store_id, and treat it like a password.


2. Calling the API

Base URL:  https://admin.api.frontspace.se/api/v1
Auth:      Authorization: Bearer fsapp_…
Version:   Frontspace-Api-Version: 2026-10
  • Send Frontspace-Api-Version: 2026-10 on every call. On /orders it selects the order shape described here; without it you get an older shape kept for existing plugins. An unknown version is a 400 UNSUPPORTED_API_VERSION, never a silent fallback.
  • The store is decided by the token. No endpoint takes a store id.
  • Rate limits (per app, per store): 60 reads and 120 writes a minute. Over that you get 429; back off and retry.
  • Request bodies: JSON, at most 1 MB. Unknown fields are refused with a 400 rather than ignored, so a typo cannot silently drop data.
  • Money is a decimal string with two places, in the order's currency ("192.00"), so nothing rounds through a float.
  • Errors are JSON: { "error": "<human message>", "code": "<MACHINE_CODE>" }, sometimes with details. Branch on code, never on error.
  • IP allowlist (optional). Give us your fixed egress IPs and we lock the app to them; any other address gets 403 IP_NOT_ALLOWED.

2.1 Idempotency

POST /orders and POST /orders/:id/fulfillment require an Idempotency-Key header: 1–255 visible ASCII characters, unique per action. Generate one per action (a UUID is fine) and reuse it when you retry that same action.

Situation Answer
A repeat of a completed request The first answer again, with Idempotent-Replayed: true
The same key with a different body 422 IDEMPOTENCY_KEY_REUSED
The first request is still running 409 IDEMPOTENCY_IN_PROGRESS; retry shortly
A 5xx, 409 or 429 Not stored: retry with the same key

Keys are kept for 24 hours. For orders, the key is also stored on the order itself, so one key can never place two orders, even after those 24 hours.

2.2 Auth errors

HTTP code Meaning
401 APP_AUTH_REQUIRED No Bearer fsapp_… header
401 INVALID_TOKEN Unknown or revoked token: the store has uninstalled you
403 INSTALLATION_INACTIVE / APP_INACTIVE The installation or app is switched off
403 IP_NOT_ALLOWED Called from an address outside your allowlist
403 SCOPE_REQUIRED Your installation lacks the scope this endpoint needs (INSUFFICIENT_SCOPE on /events when no scope allows any event)

3. The menu and pausing orders

GET /food/locations — read_food_menu

Where the restaurant takes orders, and whether it does so right now.

{
  "locations": [{
    "id": "<uuid>",
    "name": "Kungsgrillen Centrum",
    "address": "…",
    "phone": "…",
    "timezone": "Europe/Stockholm",
    "hours": { "mon": ["10:00", "21:00"], "tue": ["10:00", "21:00"] },
    "open_now": true,
    "closes_at": "21:00",
    "opens_at": null,
    "accepting_orders": true,
    "paused_until": null,
    "prep_minutes": 20
  }]
}
  • hours uses the location's own timezone. A missing day means closed. hours: null means unknown, and then hours never block an order.
  • accepting_orders: false means the kitchen has paused orders from its screen. That includes yours. paused_until says when the pause ends by itself; null means until someone resumes it.

GET /food/menu?location=<id>&channel=takeaway — read_food_menu

The orderable menu for one location and channel. channel is takeaway (the default) or dine_in. location may be left out when the store has a single location.

{
  "menu": {
    "location_id": "<uuid>",
    "channel": "takeaway",
    "currency": "SEK",
    "next_price_change_at": "2026-10-10T16:00:00.000Z",
    "categories": [{ "id": "<uuid>", "name": "Burgare", "parent_id": null }],
    "products": [{
      "id": "<uuid>",
      "name": "Cheeseburgare",
      "price": "119.00",
      "image_url": "https://…",
      "vegetarian": false,
      "dietary": [],
      "category_ids": ["<uuid>"],
      "option_groups": [{
        "id": "<uuid>",
        "name": "Välj dryck",
        "kind": "choice",
        "min_select": 1,
        "max_select": 1,
        "items": [{ "id": "<uuid>", "name": "Cola", "price_delta": "0.00", "max_quantity": 1, "default": false }]
      }]
    }]
  }
}
  • kind: "removal" groups list ingredients the customer may leave out, at no charge.
  • Re-read the menu at next_price_change_at. Scheduled prices (for example an evening price) change the menu at that time.
  • A price-on-request dish is not on the menu: it cannot be ordered through the API. Whether a dish can be sold right now is checked again when you place the order (UNSELLABLE_ITEMS, OUT_OF_STOCK).
HTTP code Meaning
404 FOOD_NOT_ENABLED The store does not take food orders
400 LOCATION_REQUIRED The store has several locations; pick one (they are listed in the answer)
404 LOCATION_NOT_FOUND No such location in this store
400 INVALID_CHANNEL channel is not takeaway or dine_in

PATCH /food/locations/:id — write_food_locations

Pause or resume orders, or change the preparation time. The field names are the ones GET /food/locations returns.

{ "accepting_orders": false, "paused_until": "2026-10-10T18:30:00+02:00", "pause_reason": "Köket är fullt" }
{ "accepting_orders": true }
{ "prep_minutes": 25 }
Field
accepting_orders false pauses, true resumes. Resuming clears the end time and the reason
paused_until When the pause ends by itself: in the future, at most 7 days ahead. Only with accepting_orders: false. Leave it out to pause until someone resumes
pause_reason Shown to the restaurant's staff, at most 200 characters. Only with accepting_orders: false. It is never returned by the API
prep_minutes The kitchen's usual preparation time, 1–240, or null

The answer is the location, exactly as GET /food/locations shows it, and the fields you changed:

{ "location": { "id": "<uuid>", "accepting_orders": false, "paused_until": "2026-10-10T16:30:00.000Z", … }, "changed": ["accepting_orders", "paused_until", "pause_reason"] }

It is the restaurant's one pause. It is the same switch the kitchen screen and the till use: while paused, both web orders and phone orders are refused, and sales at the till go on. Whoever changed it last, you or the restaurant's staff, decides. Subscribe to food_location.updated to hear about the changes you did not make yourself.

Every field is checked before anything is written: one invalid field and nothing changes.

HTTP code Meaning
400 INVALID_FIELDS A value is invalid, or the field is unknown; details[] names each one, and writable lists what you may send
403 FIELD_NOT_WRITABLE A field your app may not change, such as the restaurant's own kitchen settings
400 INVALID_BODY The body is not a JSON object
404 LOCATION_NOT_FOUND No such location in this store
404 FOOD_NOT_ENABLED The store does not take food orders

4. Placing an order

POST /orders — create_orders, Idempotency-Key required

{
  "location_id": "<uuid>",
  "channel": "takeaway",
  "pickup_at": "2026-10-10T17:30:00+02:00",
  "customer": { "name": "Anna", "phone": "+46701234567", "email": "anna@example.com" },
  "note": "Ring på när den är klar",
  "payment": { "method": "pay_at_pickup" },
  "expected_total": "238.00",
  "line_items": [
    { "product_id": "<uuid>", "quantity": 2,
      "options": [{ "item_id": "<uuid>" }, { "item_id": "<uuid>", "quantity": 2 }] }
  ]
}
Field
location_id Optional when the store has one location
channel takeaway (default) or dine_in
pickup_at ISO 8601 with a timezone, at most 3 days ahead. Leave it out for as soon as possible
customer name and phone are required. The phone is what the kitchen calls back on
note At most 500 characters; printed for the kitchen
payment.method Only pay_at_pickup. The customer pays at the restaurant's till
expected_total Recommended. The total you told the customer. It is a check, never a price: if our total differs, nothing is placed and you get 409 PRICE_CHANGED
line_items 1–50 lines. Each has quantity 1–50 and options with item_ids from that dish's option_groups

Frontspace prices the order. You never send a price. We check every line against the menu, the location's hours and pause state, stock, and the dish's option rules.

201 for a new order, 200 when the key already placed one:

{
  "order": {
    "id": "<uuid>",
    "number": "1058",
    "status": "processing",
    "payment": { "method": "pay_at_pickup", "status": "pending" },
    "currency": "SEK",
    "subtotal": "190.40",
    "tax": "47.60",
    "total": "238.00",
    "location_id": "<uuid>",
    "channel": "takeaway",
    "pickup_at": "2026-10-10T15:30:00.000Z",
    "created_at": "2026-10-10T15:02:11.000Z",
    "line_items": [{
      "id": "<uuid>", "product_id": "<uuid>", "name": "Cheeseburgare", "quantity": 2,
      "unit_price": "119.00", "total": "238.00",
      "options": [{ "item_id": "<uuid>", "name": "Cola", "quantity": 1 }]
    }]
  }
}

This answer leaves out the customer's details on purpose, because it is stored for idempotency. Read the full order with GET /orders/:id.

The order goes straight to the restaurant's kitchen screen, with the caller's name and phone. In the order API its channel is phone.

HTTP code What to do
400 INVALID_BODY Fix the request (error has field-level detail)
422 INVALID_LINE_ITEMS A line does not match the menu; details[] names the line and reason (not_on_menu, option_not_on_dish, duplicate_option, at_most_<n>, requires_at_least_<n>, allows_at_most_<n>)
422 UNSELLABLE_ITEMS A dish cannot be sold right now; details[] names it
422 INVALID_OPTIONS The options break a dish's rules
409 OUT_OF_STOCK Sold out since you read the menu
409 PRICE_CHANGED The total is not expected_total; re-read the menu and tell the customer the new total
409 LOCATION_CLOSED Closed now, or at pickup_at
409 LOCATION_PAUSED The kitchen has paused orders
409 LOCATION_INACTIVE The location does not take orders
422 PICKUP_IN_PAST / PICKUP_TOO_FAR pickup_at is in the past, or more than 3 days ahead
404 FOOD_NOT_ENABLED / LOCATION_NOT_FOUND As for the menu
400 LOCATION_REQUIRED Name a location_id
503 MENU_UNAVAILABLE / UNAVAILABLE Retry with the same Idempotency-Key
500 INTERNAL Retry with the same Idempotency-Key

⭐ After a timeout or a 5xx, always retry with the same key. The order may have been placed: the retry then returns that order, and never places a second one.

POST /orders/:id/cancel — create_orders

Cancels an order you placed that is not yet paid: the caller rang back, or never came. No body and no idempotency key; cancelling twice is safe.

{ "order": { "id": "<uuid>", "number": "1058", "status": "cancelled" }, "changed": true }

The second time, changed is false. The order leaves the kitchen screen, and its stock goes back.

HTTP code Meaning
404 ORDER_NOT_FOUND Not an order your app placed in this store
409 ORDER_NOT_CANCELLABLE Already paid, or past the stage where you may cancel. A paid order is refunded by the restaurant
409 ORDER_BEING_PAID The till is taking payment for it right now
409 ORDER_CHANGED It changed while you cancelled; read it again

5. Reading orders

GET /orders — read_orders

All the store's orders, not only yours, oldest first.

Query
limit 1–100, default 50
cursor next_cursor from the previous page
created_after ISO 8601 with a timezone
status One of pending, paid, confirmed, processing, shipped, delivered, completed, cancelled, refunded
include food adds the kitchen's own state (below)

GET /orders/:id — read_orders

{ "order": { … } }, in the shape below. Takes include as well.

The order (2026-10)

{
  "id": "<uuid>",
  "api_version": "2026-10",
  "order_number": "1058",
  "created_at": "2026-10-10T15:02:11.000Z",
  "updated_at": "2026-10-10T15:20:40.000Z",
  "status": "processing",
  "financial_status": "pending",
  "cancelled_at": null,
  "channel": "phone",
  "currency": "SEK",
  "subtotal": "238.00",
  "discount_total": "0.00",
  "shipping_amount": "0.00",
  "tax_amount": "47.60",
  "tip_amount": "0.00",
  "total_amount": "238.00",
  "refunded_amount": "0.00",
  "note": "Ring på när den är klar",
  "fulfillment": {
    "type": "pickup",
    "status": "in_progress",
    "location_id": "<uuid>",
    "pickup_at": "2026-10-10T15:30:00.000Z",
    "service": { "handle": "kitchen", "name": "Kök" }
  },
  "line_items": [{
    "id": "<uuid>", "product_id": "<uuid>", "variant_id": null, "sku": null,
    "name": "Cheeseburgare", "quantity": 2,
    "unit_price": "119.00", "total_price": "238.00", "discount_amount": "0.00",
    "options": [{ "group_id": "<uuid>", "group_name": "Välj dryck", "item_id": "<uuid>", "item_name": "Cola" }],
    "parent_line_id": null
  }],
  "extensions": {
    "food": { "kitchen_status": "preparing", "kitchen_note": null, "dining": "takeaway" }
  },
  "customer": { "id": null, "first_name": "Anna", "last_name": null, "email": "anna@example.com", "phone": "+46701234567" },
  "shipping_address": null
}
  • channel is online, pos, kiosk or phone (orders placed through this API).
  • financial_status becomes paid when the customer pays at the till; you get an order.updated event when that happens.
  • fulfillment.status is the general progress: unfulfilled, in_progress, ready, fulfilled or cancelled.
  • extensions.food is present only with include=food. kitchen_status is pending, new, preparing, ready, picked_up or rejected.
  • customer and shipping_address are present only with read_customers.
  • New fields may be added within 2026-10; ignore fields you do not know. Nothing is removed or changed within a version.

6. Moving the kitchen status

POST /orders/:id/fulfillment — write_merchant_managed_fulfillment_orders, Idempotency-Key required

{ "status": "in_progress", "extensions": { "food": { "prep_minutes": 20 } } }
status In the kitchen Notes
in_progress Accepted, being prepared Only accepting takes extensions.food.prep_minutes (1–240)
ready Ready for pickup The order must be accepted first (409 NOT_ACCEPTED)
fulfilled Picked up
cancelled Rejected Only a rejection takes a note (at most 200 characters), the reason shown to staff
{
  "order_id": "<uuid>",
  "fulfillment": { "status": "in_progress", "service": { "handle": "kitchen", "name": "Kök" }, "changed": true }
}

Repeating the current status answers 200 with changed: false.

The restaurant's own kitchen screen moves the same orders. If both move an order at once, one of you gets 409 CONFLICT: read the order and decide again.

HTTP code Meaning
404 ORDER_NOT_FOUND No such order in this store
409 INVALID_TRANSITION Not a move the kitchen can make from where the order is
409 NOT_ACCEPTED Move it to in_progress first
409 NOT_IN_KITCHEN The order is no longer in the kitchen's queue
409 CONFLICT It moved while you asked; read it and retry
403 FULFILLMENT_NOT_PERMITTED Your app may not move orders fulfilled by this service
409 NO_FULFILLMENT_SERVICE Nothing fulfils this order (for example, a shipped web order)
422 INVALID_PREP_MINUTES / PREP_ONLY_WHEN_ACCEPTING prep_minutes is outside 1–240, or sent on anything but accepting
422 NOTE_ONLY_WHEN_CANCELLED / NOTE_TOO_LONG note on anything but a rejection, or over 200 characters
400 UNKNOWN_EXTENSION / UNKNOWN_EXTENSION_FIELD Only extensions.food.prep_minutes is accepted

7. Being told: webhooks

POST /webhooks

One subscription per app per store.

{
  "events": ["order.created", "order.updated", "order.cancelled", "order.refunded", "order.fulfillment_updated", "food_location.updated"],
  "target_url": "https://partner.example.com/frontspace/webhooks",
  "include": ["food"]
}

201: { "webhook": { "id": "<uuid>", … }, "secret": "whsec_…" }

⚠️ The secret is shown once, in this answer only. Store it as a secret. If you lose it, rotate.

Endpoint
GET /webhooks Your subscription (never its secret), delivery health, and the events and includes you may use
PUT /webhooks/:id Change events, include or target_url, or pause it with is_active: false
POST /webhooks/:id/rotate-secret A new secret now. The old one keeps working for 24 hours, and during that time each delivery is signed with both
DELETE /webhooks/:id Stop deliveries

target_url must be https and publicly reachable. We do not follow redirects.

Events

Event When
order.created An order is placed: online, at the till, at a kiosk, or by you
order.updated Anything about it changed, payment included
order.cancelled It was cancelled
order.refunded Money was returned
order.fulfillment_updated The kitchen status moved: by the kitchen screen, the till, or you
food_location.updated A location changed: orders paused or resumed (by the kitchen screen, the till, the admin, you, or a timed pause running out), preparation time, opening hours, name, address, phone, or active
food_location.created The restaurant added a location

Order events need read_orders; customer details in them need read_customers. Location events need read_food_menu. A location event carries data.food_location, the location exactly as GET /food/locations shows it, or null once it is no longer active:

{
  "id": "<event id>",
  "type": "food_location.updated",
  "timestamp": "2026-10-10T16:00:00.000Z",
  "data": { "sequence": 48230, "changed": ["accepting_orders", "paused_until"], "food_location": { "id": "<uuid>", "accepting_orders": true, "paused_until": null, … } }
}

A timed pause is announced within about a minute of its paused_until.

The delivery

POST <target_url>
Content-Type: application/json
webhook-id: <event id>
webhook-timestamp: <unix seconds>
webhook-signature: v1,<base64 signature>
{
  "id": "<event id>",
  "type": "order.fulfillment_updated",
  "timestamp": "2026-10-10T15:20:40.000Z",
  "data": {
    "sequence": 48211,
    "changed": ["fulfillment.status", "extensions.food.kitchen_status"],
    "order": { … the order, exactly as in §5 … }
  }
}
  • data.order is the order's current state when we send, not a snapshot from when the event happened. If events reach you out of order, keep the one with the highest data.sequence.
  • data.changed lists what changed, using the paths of the order shape.
  • data.order is null if the order can no longer be read.

Verifying the signature

We follow Standard Webhooks. Use their library for your language, or do this:

  1. Take the bytes of the secret after whsec_, base64-decoded. That is the HMAC key.
  2. Compute base64( HMAC-SHA256( key, "<webhook-id>.<webhook-timestamp>.<raw body>" ) ).
  3. webhook-signature holds one or more space-separated v1,<signature> values. Accept the request if any of them matches, comparing in constant time. There are two during a rotation.
  4. Reject a webhook-timestamp more than 5 minutes from your clock.
import { createHmac, timingSafeEqual } from 'node:crypto'

function verify(secret: string, headers: Headers, rawBody: string): boolean {
  const id = headers.get('webhook-id') ?? ''
  const ts = headers.get('webhook-timestamp') ?? ''
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64')
  const expected = Buffer.from(createHmac('sha256', key).update(`${id}.${ts}.${rawBody}`).digest('base64'))
  return (headers.get('webhook-signature') ?? '').split(' ').some((part) => {
    const sig = Buffer.from(part.replace(/^v1,/, ''))
    return sig.length === expected.length && timingSafeEqual(sig, expected)
  })
}

Verify against the raw body, before any JSON parsing.

Answering, retries and duplicates

  • Answer 2xx within 10 seconds. Do the work afterwards, from a queue on your side.
  • Anything else, or a timeout, is retried: 8 attempts in all, the wait doubling from 30 seconds, so about an hour from first to last. A redirect is a failure and is not retried.
  • After 20 failures in a row, with no success in the last 24 hours, deliveries pause for 24 hours. GET /webhooks shows delivery.paused_until and the last error.
  • The same event can arrive more than once. Deduplicate on webhook-id.

8. Catching up: the event feed

GET /events

Every event you could have received in the last 14 days, in order: the types your scopes allow (read_orders for order events). These are the same envelopes, with the same ids, that webhooks deliver. Use it to recover after an outage longer than the retries (about an hour), after a fresh install, or as your only channel if you would rather poll.

Query
cursor next_cursor from the previous page. Leave it out to start at the beginning of the 14 days
limit 1–100, default 50
types A comma-separated list of event types. Default: every type your scopes allow
include food, as on /orders
{ "events": [ { "id": "…", "type": "order.created", "timestamp": "…", "data": { … } } ], "next_cursor": "…", "has_more": false }
  • Save next_cursor after you have processed a page, and resume from it.
  • The newest ~30 seconds are held back until they are settled, so a page never skips an event that commits late.
  • 410 CURSOR_EXPIRED: your cursor is older than 14 days. Re-read the orders you care about with GET /orders, then start the feed again without a cursor.

9. One phone order, end to end

  1. Before the call: GET /food/locations. Check accepting_orders and open_now (or the hours at the requested pickup time). If your system decides the kitchen is full, pause it with PATCH /food/locations/:id, and resume it the same way.
  2. GET /food/menu?location=… (cache it until next_price_change_at).
  3. Place it: POST /orders with a fresh Idempotency-Key and expected_total. On 409 PRICE_CHANGED, OUT_OF_STOCK or LOCATION_PAUSED, tell the caller.
  4. The order appears on the kitchen screen. You receive order.created.
  5. The kitchen accepts it. If your dashboard is where staff work, POST /orders/:id/fulfillment with in_progress and prep_minutes. If the restaurant's own screen is used, you receive order.fulfillment_updated.
  6. ready, then fulfilled when it is picked up. Each step comes back to you as order.fulfillment_updated.
  7. Payment happens at the till. You receive order.updated with financial_status: "paid".
  8. The caller cancels: POST /orders/:id/cancel, as long as the order is unpaid.

10. Before going live

On the Teststore, show us:

  • Install callbacks are verified, and the token is stored per store.
  • An order placed through POST /orders appears on the kitchen screen.
  • A retry with the same Idempotency-Key returns the same order.
  • PRICE_CHANGED and LOCATION_PAUSED are handled with the caller.
  • A kitchen status change made on our screen reaches your webhook, with the signature verified.
  • A status change from your side shows on our screen.
  • A cancellation through the API removes the order from the kitchen.
  • A pause from your system refuses new orders, and a resume lets them through again.
  • Your endpoint deduplicates on webhook-id, and you can resume from the feed.

The restaurant then installs your app, and you receive its token.

We also need a data processing agreement in place before you receive real customers' names and phone numbers.

Image