Developers · Integrations & webhooks

Plug Andromeda into anything.

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.

Four things to integrate — and one signing secret.

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.

Couriers — Queries & Commands

  • GET /organizations/{orgId}/courierproviders/locations/{locId}
  • GET /organizations/{orgId}/courierconfigurations/locations/{locId}
  • GET /organizations/{orgId}/courierjobs
  • GET /organizations/{orgId}/courierjobs/{id}/quotes
  • POST /organizations/{orgId}/courierjobs
  • POST /organizations/{orgId}/courierjobs/{id}/cancel

Payment devices — Queries & Commands

  • GET /organizations/{orgId}/paymentdevices
  • GET /organizations/{orgId}/paymentdevices/{id}
  • POST /organizations/{orgId}/paymentdevices
  • PUT /organizations/{orgId}/paymentdevices/{id}
  • POST /organizations/{orgId}/paymentdevices/{id}/test

Webhooks & subscriptions — Commands

  • GET /organizations/{orgId}/webhooks
  • POST /organizations/{orgId}/webhooks
  • PUT /organizations/{orgId}/webhooks/{id}
  • DELETE /organizations/{orgId}/webhooks/{id}
  • POST /organizations/{orgId}/webhooks/{id}/rotate-secret

Webhook event types

Subscribe to any subset. Every event carries the same outer envelope — eventId, eventType, occurredAtUtc, organizationId, data.

Ordersorder.created
Ordersorder.status.changed
Ordersorder.cancelled
Ordersorder.refunded
Customerscustomer.created
Customerscustomer.contact-preferences.changed
Loyaltyloyalty.points.adjusted
Loyaltyloyalty.tier.changed
Menuproduct.pricing.changed
Menuproduct.availability.changed
Courierscourier-job.status.changed
Paymentspayment.settled
Always verify the signature. Every inbound webhook carries 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.

Six integrations you’ll want to wire up.

Subscribe to a webhook. Verify a signature. Book a courier. Register a Stripe reader. Listen for an order event. Handle a retry cleanly.

1. Subscribe to webhooks

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
Request
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"
}

2. Verify an inbound webhook signature

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.

NODE
Express middleware
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();
  };
}

3. Book a courier for an order

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.

POST
Step 1 — get quotes
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/..."
}

4. Register a Stripe Terminal reader

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
Request
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"
}

5. Handle an order event with idempotency

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.

NODE
Inbound handler
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"
  }
}

6. Retry policy, delivery attempts, dead-lettering

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
Inspect delivery attempts
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.

Explore the rest of the developer platform.

Build something on top of Andromeda.

Test environment, sandbox tenant, sample data, real endpoints. No commitment.