Skip to content

Webhooks

We POST a JSON event to your https endpoint when something happens, signed with that endpoint’s secret following Standard Webhooks, the format OpenAI, Twilio and Resend use.

Event When data
lead.created A buyer or dealer saves a build (“Get my build”) lead_id, build_id, dealership, vehicle, items, total, weekly, currency, marketing_consent, status, updated_at
build.created The same moment, without the person (for analytics) build_id, vehicle, items, total, currency, created_at
quote.sent The quote is emailed to the buyer lead_id, build_id, dealership, status: "sent", total, currency, updated_at
lead.won A dealer marks the deal won lead_id, build_id, dealership, status: "won", total, currency, updated_at

With Include customer details on for the endpoint, lead.created, quote.sent and lead.won also carry customer (name, email, mobile), rego and salesperson. build.created never does.

{
"id": "evt_d1024fc881e9becfc1c23952",
"type": "lead.created",
"timestamp": "2026-09-30T06:03:22.982Z",
"api_version": "2026-10-01",
"tenant": "yourbrand",
"data": {
"lead_id": "lead_542",
"build_id": "bld_497c186a9058f43189e75b3ab5af4495",
"dealership": "Norris Group",
"vehicle": { "name": "Toyota Hilux Double Cab SR5 2026", "colour": "Crystal Pearl" },
"items": [
{ "sku": "G4X4-GLASSCANOPY-HILUX", "name": "Alloy Canopy, Lift-up Glass Doors", "qty": 1, "unit_price": 4950 },
{ "sku": "G4X4-BULLBAR-4X4", "name": "4x4 Alloy Bull Bar", "qty": 1, "unit_price": 2650 }
],
"total": 10403,
"weekly": 46,
"currency": "AUD",
"marketing_consent": false,
"status": "proposed",
"updated_at": "2026-09-30T06:03:22.981Z"
}
}

Each request carries three headers:

webhook-id: evt_d1024fc881e9becfc1c23952
webhook-timestamp: 1790748203
webhook-signature: v1,U3ZuJArKQ9Gbfhv7lDc...

The signature is base64(HMAC-SHA256(secret, id + "." + timestamp + "." + raw body)), where the secret is the base64 part after whsec_. Before trusting a body:

  1. Use the raw body, exactly as received (do not re-serialise the JSON).
  2. Compare against every v1, value in the header (during a secret change there are two, separated by a space) with a constant-time comparison.
  3. Refuse a timestamp more than 5 minutes from your clock.
  4. Remember webhook-id: the same event can arrive twice. Ignore one you have handled.

A Standard Webhooks library does all four: npm install standardwebhooks (Node), pip install standardwebhooks (Python), or see verify_webhook.py for the same check in plain Python.

import { Webhook } from 'standardwebhooks';
const event = new Webhook(process.env.GOASK_WEBHOOK_SECRET).verify(rawBody, request.headers); // throws if not ours
  • Answer 2xx within 10 seconds, then do slow work. Anything else counts as a failure. Redirects are not followed.
  • Retries: right away, then after 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 10 h and daily, for about 5 days. The event id stays the same on every retry.
  • Switched off: an endpoint that answers 410 Gone, or has not accepted a delivery for 5 days, is switched off and you get an email. Switch it back on from /developer.
  • Replay: /developer lists the last 50 deliveries; Send again repeats one with the same id.
  • Order is not guaranteed. Use updated_at, and read the lead when in doubt.
  • Your URL: https only, on a public address.

A secret is shown once when the endpoint is made. New secret on /developer makes another one; the old one keeps signing too for 24 hours, so you can switch without missing a delivery.