Courier providers. Payment devices. Third-party integrators. Outbound webhooks you subscribe to. Inbound webhooks Andromeda calls on your endpoint, signed with HMAC-SHA256 so you can verify every byte.
Every event on the platform. Delivered, signed, idempotent, with a retry policy you can tune.
Courier providers deliver orders (Uber Direct first-party; others via Stuart or Andromeda-managed dispatch). Payment devices are Stripe Terminal readers bound to a location. Third-party integrators are external systems (your CRM, your accounting pipe, a bespoke BI tool) authorised against your tenant. Webhooks are the inbound channel for everything that happens — subscribe, receive, verify signature, acknowledge.
Subscribe to any subset. Every event carries the same outer envelope — eventId, eventType, occurredAtUtc, organizationId, data.
X-Andromeda-Signature: sha256=<hex>. Compute HMAC-SHA256 of the raw request body using your webhook secret and compare in constant time. Reject mismatches with 401 Unauthorized. Never trust the payload before verification.
Subscribe to a webhook. Verify a signature. Book a courier. Register a Stripe reader. Listen for an order event. Handle a retry cleanly.
Tell Andromeda where to deliver events. You pick the URL, the event types, and an HTTPS endpoint on your side that can respond within 5 seconds. The response includes the signing secret — shown once, store it safely.
POST /organizations/{orgId}/webhooks
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Production order stream",
"url": "https://hooks.example.com/andromeda",
"eventTypes": [
"order.created",
"order.status.changed",
"order.refunded"
],
"active": true
}
Response — 201 Created
{
"id": "whk_f18c...",
"name": "Production order stream",
"url": "https://hooks.example.com/andromeda",
"eventTypes": ["order.created", "order.status.changed", "order.refunded"],
"secret": "whsec_9f2a…", // shown ONCE — store it
"active": true,
"createdAtUtc": "2026-04-19T11:47:03Z"
}
Compute HMAC-SHA256 over the raw request body using your stored secret, compare against the X-Andromeda-Signature header. Reject mismatches. Reject bodies older than 5 minutes to prevent replay.
import crypto from "crypto";
export function verifyAndromedaWebhook(secret) {
return (req, res, next) => {
const header = req.headers["x-andromeda-signature"] || "";
const [scheme, received] = header.split("=");
if (scheme !== "sha256" || !received) {
return res.status(401).send("bad signature header");
}
const expected = crypto
.createHmac("sha256", secret)
.update(req.rawBody) // raw bytes — not reparsed JSON
.digest("hex");
const ok =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!ok) return res.status(401).send("signature mismatch");
// Reject replayed payloads older than 5 minutes
const occurred = new Date(req.body.occurredAtUtc).getTime();
if (Date.now() - occurred > 5 * 60 * 1000) {
return res.status(401).send("stale webhook");
}
next();
};
}
Get a quote first (distance, ETA, price), then create the job. Andromeda handles the handshake with Uber Direct or Stuart; you just get status events back on the courier-job.status.changed webhook.
GET /organizations/{orgId}/courierjobs/quotes
?LocationId=e8a2...
&OrderId=c8d1...
Authorization: Bearer <token>
Step 1 response — 200 OK
{
"quotes": [
{ "provider": "UberDirect", "fee": 4.80, "etaMinutes": 28, "quoteId": "q_4c9a..." },
{ "provider": "Stuart", "fee": 5.20, "etaMinutes": 31, "quoteId": "q_d12f..." }
]
}
Step 2 — book the job
POST /organizations/{orgId}/courierjobs
Authorization: Bearer <token>
Content-Type: application/json
{
"orderId": "c8d1...",
"locationId": "e8a2...",
"quoteId": "q_4c9a..."
}
Step 2 response — 202 Accepted
{
"id": "cj_3e1f...",
"status": "Created",
"provider": "UberDirect",
"trackingUrl": "https://track.uber.com/..."
}
Bind a Stripe Terminal reader to a location so in-store and kiosk payments route to it. Andromeda handles the Stripe Connect onboarding and reader registration; you just tell us the reader ID and where it lives.
POST /organizations/{orgId}/paymentdevices
Authorization: Bearer <token>
Content-Type: application/json
{
"locationId": "e8a2...",
"provider": "StripeTerminal",
"stripeReaderId": "tmr_FxiBUmVOx6...",
"label": "Front counter",
"usage": ["Till", "Kiosk"]
}
Response — 201 Created
{
"id": "pd_8a21...",
"locationId": "e8a2...",
"provider": "StripeTerminal",
"stripeReaderId": "tmr_FxiBUmVOx6...",
"label": "Front counter",
"usage": ["Till", "Kiosk"],
"status": "Online",
"lastSeenUtc": "2026-04-19T11:47:08Z"
}
Andromeda retries on 5xx and timeouts. Use eventId as your idempotency key — upsert, don’t insert. Ack fast (200 OK under 5s), process asynchronously if you can’t.
app.post(
"/andromeda/webhooks",
verifyAndromedaWebhook(process.env.ANDROMEDA_WEBHOOK_SECRET),
async (req, res) => {
const { eventId, eventType, occurredAtUtc, data } = req.body;
// Idempotency — upsert by eventId, ignore dupes
const inserted = await db.events.insertIfAbsent({
eventId, eventType, occurredAtUtc, payload: data
});
if (!inserted) {
return res.status(200).send("ack (duplicate)");
}
// Ack fast, process off the request thread
res.status(200).send("ack");
queue.enqueue({ eventId, eventType, data });
}
);
Inbound payload shape
{
"eventId": "evt_01HQZ9F2K8...", // unique, use as idempotency key
"eventType": "order.status.changed",
"occurredAtUtc": "2026-04-19T12:47:03Z",
"organizationId": "3f4a...",
"data": {
"orderId": "c8d1...",
"previousStatus": "Preparing",
"newStatus": "Ready"
}
}
If your endpoint returns non-2xx or times out, Andromeda retries with exponential backoff for 24 hours. You can inspect every attempt, manually replay failed deliveries, and rotate the signing secret without downtime.
GET /organizations/{orgId}/webhooks/{id}/deliveries
?status=Failed
Authorization: Bearer <token>
Response — 200 OK
{
"deliveries": [
{
"id": "dlv_71a2...",
"eventId": "evt_01HQZ9F2K8...",
"status": "Failed",
"attempts": 6,
"lastAttemptUtc": "2026-04-19T13:12:18Z",
"nextAttemptUtc": "2026-04-19T15:12:18Z",
"lastResponseStatus": 504,
"lastResponseBody": "upstream timeout"
}
]
}
Replay a delivery
POST /organizations/{orgId}/webhooks/{id}/deliveries/{deliveryId}/replay
Authorization: Bearer <token>
Rotate the signing secret
POST /organizations/{orgId}/webhooks/{id}/rotate-secret
Authorization: Bearer <token>
// Response includes NEW secret. OLD secret remains valid for 24h for zero-downtime rollover.
Auth, environments, your first API call.
Products, modifiers, prices, allergens, deals.
Live orders, history, refunds, webhooks.
Profiles, loyalty progress, marketing segments.
Sales metrics, operational reports, BI exports.
API surface, auth, the platform at a glance.
Test environment, sandbox tenant, sample data, real endpoints. No commitment.