🧾OrderPage

Receive orders from OrderPage

For developers of a seller's billing, delivery or inventory software.

A seller on OrderPage can have every new order sent to their own software the moment it arrives. They paste your web address in the app (Settings › Send orders to your software) and send you a secret key. This page is everything you need to receive the orders.

We only send. There is no API to call and nothing to log in to. The key is only for checking that a request really came from OrderPage.

1. What you need from the seller

2. What we send

One POST for each new order, with a JSON body and these headers:

HeaderValue
Content-Typeapplication/json; charset=utf-8
X-OrderPage-Eventorder.created, or test when the seller taps Connect or Send a test order
X-OrderPage-TimestampWhen we sent it, in Unix seconds
X-OrderPage-Signaturev1= + hex HMAC-SHA256 of timestamp + "." + body, with the secret key
{
  "event": "order.created",
  "orderId": "a8Kq2LmZ0pRtY7wX",
  "ref": "165729",
  "createdAt": "2026-09-28T11:27:12.000Z",
  "status": "pending",
  "mode": "delivery",
  "store": { "id": "GI87Kq7Y", "name": "Surmai Seafood" },
  "customer": { "name": "Asha", "phone": "+919800000000", "address": "Flat 4, Example Road, Dadar",
                "landmark": "Opposite the post office", "area": "Dadar West", "city": "Mumbai", "postcode": "400028" },
  "items": [ { "name": "Pomfret (Large)", "qty": 2, "price": 650, "lineTotal": 1300 } ],
  "fields": [ { "label": "Preparation", "value": "Curry cut" }, { "label": "Delivery time", "value": "Morning" } ],
  "dueDate": "2026-09-29",
  "note": "Please call before coming",
  "subtotal": 1300, "deliveryFee": 0, "charges": [], "total": 1300,
  "currency": "INR"
}
FieldMeaning
orderIdUnique for the order. Use this as your key.
refThe number the customer sees. It is made from the time of day and repeats on other days, so never use it as a key.
items[].nameThe item, with the chosen option in brackets, e.g. Pomfret (Large).
items[].addonsOnly when the customer picked add-ons for a dish, e.g. Spice: Hot, Extra cheese. Their price is already in price and lineTotal.
customerName, phone and the typed address. landmark, area, city and postcode are filled when the customer shared their location or typed a landmark, and are empty strings otherwise. The map location itself is not sent.
fieldsThe seller's own questions and the customer's answers, as label and value.
dueDateThe first date the customer filled (delivery or booking date), yyyy-MM-dd, or null.
modedelivery, pickup or inquiry.
booking, stay, tableOnly for appointment, stay or rental stores, and table orders.

Amounts are numbers in the store's currency. New fields may be added; ignore ones you don't know.

3. Check every request

  1. Read the raw body exactly as received, before any JSON parsing.
  2. Refuse it if X-OrderPage-Timestamp is more than 5 minutes from your clock (a replay).
  3. Compute v1= + hex HMAC-SHA256 of timestamp + "." + rawBody with the key, and compare with X-OrderPage-Signature in constant time. Refuse it if they differ (401).
  4. Skip an orderId you have already saved. The same order can very occasionally arrive twice.
  5. For event: "test", answer 200 and save nothing.

Node.js

const crypto = require("crypto");

function isFromOrderPage(headers, rawBody) {
  const ts = Number(headers["x-orderpage-timestamp"]);
  if (!ts || Math.abs(Date.now() / 1000 - ts) > 300) return false;
  const want = "v1=" + crypto.createHmac("sha256", process.env.ORDERPAGE_KEY)
    .update(ts + "." + rawBody).digest("hex");
  const got = String(headers["x-orderpage-signature"] || "");
  return got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want));
}

// Express: app.post("/orderpage", express.raw({ type: "application/json" }), (req, res) => {
//   const raw = req.body.toString("utf8");
//   if (!isFromOrderPage(req.headers, raw)) return res.sendStatus(401);
//   const order = JSON.parse(raw);
//   if (order.event === "order.created") saveOnce(order.orderId, order);
//   res.sendStatus(200);
// });

PHP

$raw = file_get_contents('php://input');
$ts  = (int)($_SERVER['HTTP_X_ORDERPAGE_TIMESTAMP'] ?? 0);
$want = 'v1=' . hash_hmac('sha256', $ts . '.' . $raw, getenv('ORDERPAGE_KEY'));
if (!$ts || abs(time() - $ts) > 300 || !hash_equals($want, $_SERVER['HTTP_X_ORDERPAGE_SIGNATURE'] ?? '')) {
  http_response_code(401); exit;
}
$order = json_decode($raw, true);
if ($order['event'] === 'order.created') { /* save once per orderId */ }
http_response_code(200);

Python

import hashlib, hmac, os, time

def is_from_orderpage(headers, raw: bytes) -> bool:
    ts = headers.get("X-OrderPage-Timestamp", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        return False
    want = "v1=" + hmac.new(os.environ["ORDERPAGE_KEY"].encode(), ts.encode() + b"." + raw,
                            hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, headers.get("X-OrderPage-Signature", ""))

Check your code with this example

Key osk_testkey_0123456789abcdef, timestamp 1790678232, body {"event":"test","ref":"TEST01"} must give:

v1=4e3f5b97bc6cdbc88fe334ec69b5dfb80870768eb574c05bfcaa8f5117985eff

4. How to answer

5. Keys and addresses

Help

Email nk92.iit@gmail.com with the store's link.