Skip to content

Webhooks

Instead of polling, give GoOrder an HTTPS URL and we will POST an event to it whenever something changes.

Terminal window
curl -X PUT https://back.goorder.com/api/partner/v1/webhooks/endpoint \
-H "Authorization: Bearer $GOORDER_API_KEY" -H "Content-Type: application/json" \
-d '{ "url": "https://partner.example.com/hooks/goorder", "events": ["order.status_changed", "order.payment_updated"] }'

The first time, the response contains a signing secret (whsec_…). It is shown once — store it. events is optional; leave it out to receive everything. (Needs the webhooks:manage scope. Your GoOrder contact can also set this up for you.)

RuleDetail
Schemehttps:// only in production
ReachabilityPublic hosts only — private, loopback and link-local addresses are refused, and checked again at delivery time
RedirectsNot followed
TimeoutWe wait 8 seconds for your reply
SuccessAny 2xx response
TypeSent when
order.createdAn order is created through the API
order.status_changedThe public status changes. data.previous_status is the status before
order.payment_updatedThe payment status changes (for example a card payment succeeds)
order.refundedThe order becomes refunded or partially_refunded
webhook.testYou asked for a test event

Every event has the same envelope:

{
"id": "evt_5b1e0c7d8a9f",
"object": "event",
"type": "order.status_changed",
"created": "2026-10-12T09:52:05.000Z",
"environment": "live",
"data": {
"previous_status": "preparing",
"order": {
"id": "66f1a2b3c4d5e6f708192a3b",
"object": "order",
"status": "out_for_delivery",
"reference": "PARTNER-100234",
"…": "the same Order object the API returns"
}
}
}

The order is a full snapshot, so you rarely need to call back to fetch it.

Anyone can POST to your URL. Verify every request before acting on it.

Each request carries:

HeaderValue
GoOrder-Signaturet=<unix seconds>,v1=<hex HMAC>
GoOrder-Event-IdThe event id (same as id in the body)
GoOrder-Event-TypeThe event type
GoOrder-TimestampThe t value

The signature is HMAC-SHA256(secret, "<t>.<raw request body>"), hex-encoded. You must use the raw bytes of the body, exactly as received — not a re-serialised JSON object.

  1. Read the raw body.
  2. Split the header into t and v1.
  3. Compute HMAC-SHA256(secret, t + "." + rawBody) and compare it to v1 with a constant-time comparison.
  4. Reject if t is more than 5 minutes from your clock (replay protection).
  5. Only then parse the JSON.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
// Keep the body raw for this route — signature checks need the exact bytes.
app.post('/hooks/goorder', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.get('GoOrder-Signature') || '';
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t);
const expected = crypto
.createHmac('sha256', process.env.GOORDER_WEBHOOK_SECRET)
.update(`${parts.t}.${req.body}`) // req.body is a Buffer here
.digest('hex');
const valid =
parts.v1 &&
parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - t) < 300;
if (!valid || !fresh) return res.status(400).send('bad signature');
const event = JSON.parse(req.body.toString('utf8'));
handleEvent(event).catch(console.error); // do the work after acknowledging
res.sendStatus(200);
});

If your endpoint does not answer 2xx within 8 seconds, we retry with increasing delays:

AttemptWaits after the previous failure
1immediately
21 minute
35 minutes
430 minutes
52 hours
66 hours
712 hours

After the 7th failed attempt (about 20 hours) the delivery is marked failed. You can see every delivery with GET /webhooks/deliveries (kept for 30 days) and re-send any of them with POST /webhooks/deliveries/{id}/redeliver after fixing your endpoint.

  • Acknowledge fast. Return 200 immediately and do the work asynchronously. A slow handler causes retries.
  • Be idempotent. The same event can arrive more than once (a retry after your 200 was lost). Remember processed ids, or make your updates safe to repeat.
  • Do not assume order. Retries can reorder events. Use data.order.status and data.order.updated_at to ignore anything older than what you already have.
  • Fetch if in doubt. GET /orders/{id} always returns the current truth.
Terminal window
curl -X POST https://back.goorder.com/api/partner/v1/webhooks/test \
-H "Authorization: Bearer $GOORDER_API_KEY"

You receive a webhook.test event. For realistic traffic, create a sandbox order and advance it — each step sends a signed event with "environment": "sandbox".