What you will do
Six steps, all in the sandbox. No real money and no real customers.
- Sign up and get test keys.
- Set up signing.
- Link a test merchant.
- Publish a menu.
- Receive a test order, accept it and complete it.
- Go live.
1. Sign up and get test keys
- Open the developer dashboard at
developers.aurages.comComing soon and sign in to your partner account. At launch we create accounts ourselves; self-registration is coming soon. - Under “Keys”, create a test key. You get a key id
pk_test_xxxand a secretsk_test_xxx. - The secret is shown once. Keep it on your server only, never in an app or a browser.
- Under “Webhooks”, add an endpoint on your server, such as
https://pos.example.com/aurmap/webhooks, and save its signing secretwhsec_xxx.
Your account comes with a ready test merchant: “Test restaurant” with two branches.
export AURMAP_HOST="https://sandbox.partners.aurmap.com"
export AURMAP_KEY_ID="pk_test_xxx"
export AURMAP_KEY_SECRET="sk_test_xxx"The sandbox address works now with sandbox keys. The live address opens for each developer once their integration is reviewed and approved.
2. Set up signing
Every request carries four headers: the key id, the time, a random nonce and an HMAC signature. Details in Keys and signing. Here is a ready bash function used by the examples on every page:
aurmap() { # aurmap METHOD PATH [BODY]
local method="$1" path="$2" body="${3:-}"
local ts nonce bh sig
ts=$(date +%s)
nonce=$(openssl rand -hex 16)
bh=$(printf '%s' "$body" | openssl dgst -sha256 -hex | sed 's/^.* //')
sig=$(printf '%s\n%s\n%s\n%s\n%s' "$method" "$path" "$ts" "$nonce" "$bh" \
| openssl dgst -sha256 -hmac "$AURMAP_KEY_SECRET" -hex | sed 's/^.* //')
curl -sS -X "$method" "$AURMAP_HOST$path" \
-H "Content-Type: application/json" \
-H "X-AurMap-Key-Id: $AURMAP_KEY_ID" \
-H "X-AurMap-Timestamp: $ts" \
-H "X-AurMap-Nonce: $nonce" \
-H "X-AurMap-Signature: $sig" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY:-$(openssl rand -hex 12)}" \
${body:+--data-raw "$body"}
}The same in Node:
import crypto from 'node:crypto';
const { AURMAP_HOST, AURMAP_KEY_ID, AURMAP_KEY_SECRET } = process.env;
export function sign(method, path, ts, nonce, body) {
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const canonical = [method.toUpperCase(), path, ts, nonce, bodyHash].join('\n');
return crypto.createHmac('sha256', AURMAP_KEY_SECRET).update(canonical).digest('hex');
}
export async function aurmap(method, path, payload, idempotencyKey) {
const body = payload === undefined ? '' : JSON.stringify(payload);
const ts = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString('hex');
const headers = {
'Content-Type': 'application/json',
'X-AurMap-Key-Id': AURMAP_KEY_ID,
'X-AurMap-Timestamp': ts,
'X-AurMap-Nonce': nonce,
'X-AurMap-Signature': sign(method, path, ts, nonce, body),
};
if (method !== 'GET') headers['Idempotency-Key'] = idempotencyKey ?? crypto.randomUUID();
const res = await fetch(AURMAP_HOST + path, { method, headers, body: body || undefined });
const json = await res.json();
if (!res.ok) throw Object.assign(new Error(json.error?.code ?? 'HTTP_' + res.status), { status: res.status, error: json.error });
return json;
}And in PHP:
<?php
function aurmap_sign(string $secret, string $method, string $path, string $ts, string $nonce, string $body): string {
$canonical = implode("\n", [strtoupper($method), $path, $ts, $nonce, hash('sha256', $body)]);
return hash_hmac('sha256', $canonical, $secret);
}
function aurmap(string $method, string $path, ?array $payload = null, ?string $idempotencyKey = null): array {
$body = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));
$headers = [
'Content-Type: application/json',
'X-AurMap-Key-Id: ' . getenv('AURMAP_KEY_ID'),
'X-AurMap-Timestamp: ' . $ts,
'X-AurMap-Nonce: ' . $nonce,
'X-AurMap-Signature: ' . aurmap_sign(getenv('AURMAP_KEY_SECRET'), $method, $path, $ts, $nonce, $body),
];
if ($method !== 'GET') {
$headers[] = 'Idempotency-Key: ' . ($idempotencyKey ?? bin2hex(random_bytes(16)));
}
$ch = curl_init(getenv('AURMAP_HOST') . $path);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($body !== '') {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$json = json_decode((string) $raw, true) ?? [];
if ($status >= 400) {
throw new RuntimeException($json['error']['code'] ?? ('HTTP_' . $status), $status);
}
return $json;
}3. Link the test merchant
Ask for a link code for a branch in your system:
aurmap POST /v1/link-codes '{"level":"place","external_location_id":"pos-branch-17","external_location_name":"Test branch","scopes":["catalog:write","inventory:write","orders:read","orders:write"]}'{
"code": "7KQ2M9XA",
"display_code": "7KQ2-M9XA",
"qr_url": "https://aurmap.com/link/7KQ2M9XA",
"status": "pending",
"expires_at": "2026-10-14T12:15:00Z"
}In production the merchant scans the code and approves it. In the sandbox you can approve on their behalf, with a button in the dashboard or with this call:
aurmap POST /v1/sandbox/link-codes/7KQ2M9XA/approve '{"place_id":"plc_test_olaya"}'You receive link.created. Keep its link_id and place_id.
{
"event_id": "evt_01JA9W2C4D",
"type": "link.created",
"seq": 1,
"data": {
"link": {
"link_id": "lnk_test_2h6j4n",
"place": { "place_id": "plc_test_olaya", "name": "Test restaurant - Olaya" },
"external_location_id": "pos-branch-17",
"scopes": ["catalog:write", "inventory:write", "orders:read", "orders:write"]
}
}
}4. Publish a menu
Save a small menu as menu.json:
{
"schema": "aurmap.menu/1",
"currency": "SAR",
"default_language": "ar",
"languages": ["ar", "en"],
"tax": { "prices_include_vat": true, "vat_percent": 15, "service_charge_percent": 0 },
"aurmap_ordering": { "enabled": true, "pickup": true, "delivery": false, "cash": true },
"sections": [
{ "id": "burgers", "parent_id": null, "name": { "ar": "البرجر", "en": "Burgers" }, "sort": 0 }
],
"items": [
{
"id": "chicken-burger",
"type": "menu_item",
"section_id": "burgers",
"sort": 0,
"name": { "ar": "برجر دجاج", "en": "Chicken burger" },
"price_minor": 2300,
"sizes": [
{ "id": "m", "name": { "ar": "وسط", "en": "Medium" }, "price_minor": 2300 },
{ "id": "l", "name": { "ar": "كبير", "en": "Large" }, "price_minor": 2990 }
],
"group_ids": ["sauce"],
"order_types": { "pickup": true, "delivery": true, "dine_in": true }
}
],
"groups": [
{
"id": "sauce", "name": { "ar": "الصوص", "en": "Sauce" }, "required": true, "min": 1, "max": 1,
"options": [
{ "id": "hot", "name": { "ar": "صوص حار", "en": "Hot sauce" }, "price_minor": 230, "image": null },
{ "id": "none", "name": { "ar": "بدون صوص", "en": "No sauce" }, "price_minor": 0, "image": null }
]
}
],
"state": { "paused": false, "sold_out": { "items": [], "sizes": [], "options": [] }, "prices": { "items": [], "sizes": [], "options": [] } }
}Then publish it:
aurmap PUT /v1/places/plc_test_olaya/menu "$(cat menu.json)"{ "place_id": "plc_test_olaya", "version": 1, "prev_version": 0, "kind": "full", "status": "processing", "hash": "9f2c4be1d07a65e3c1f8a2b4d6e8f0a1b3c5d7e9f1a3b5c7d9e1f3a5b7c9d1e3", "warnings": [] }A few seconds later you receive menu.changed with the same version and hash. The full format is in Catalog. The menu is now visible in the sandbox AurMap app. All amounts are in halalas: 2300 means 23.00 in riyals.
5. Receive a test order
Ask the sandbox to place an order as if a customer had ordered from the app:
aurmap POST /v1/sandbox/orders '{"place_id":"plc_test_olaya","items":[{"item_id":"chicken-burger","size_id":"l","qty":1,"options":[{"group_id":"sauce","option_id":"hot"}]}],"payment":{"method":"card"}}'Your webhook endpoint receives order.created:
{
"delivery_id": "dlv_test_01JA9Y3K",
"mode": "test",
"events": [
{
"event_id": "evt_test_01JA9Y3J8Q",
"type": "order.created",
"seq": 3,
"data": {
"order": {
"order_id": "ord_test_7m1x9c",
"number": "M-1001",
"status": "received",
"accept_by": "2026-10-14T12:35:05Z",
"items": [ { "line_id": "1", "item_id": "chicken-burger", "size_id": "l", "qty": 1, "line_total_minor": 3220 } ],
"totals": { "currency": "SAR", "total_minor": 3220 },
"payment": { "method": "card", "status": "paid", "amount_minor": 3220 }
}
}
}
]
}Verify the signature first (see Events), then answer 200 within 10 seconds. The answer is not an acceptance. Accept the order before accept_by:
aurmap POST /v1/orders/ord_test_7m1x9c/accept '{"prep_minutes":15,"external_order_id":"POS-88231"}'
aurmap POST /v1/orders/ord_test_7m1x9c/status '{"status":"ready"}'
aurmap POST /v1/orders/ord_test_7m1x9c/status '{"status":"completed"}'{ "order_id": "ord_test_7m1x9c", "status": "completed", "version": 4 }That is your first successful order. Also try a customer cancellation and an expired accept window from the sandbox.
6. Go live
- Complete the certification checklist in the dashboard. Details in Going live.
- After certification you get live keys
pk_live_xxxandsk_live_xxx. - Switch
AURMAP_HOSTto the live address and add a live webhook endpoint. - Show a “Connect to AurMap” button to your merchants. Each merchant links their own branches.