Webhooks
Webhooks push events to your own endpoints the moment they happen — a visitor qualifies, a lead is assigned, a score changes. Use them to trigger workflows in internal tools, data warehouses or any system Aurora doesn't integrate with natively.
On this page
Create an endpoint#
- Add the URLGo to Settings → Webhooks and choose Add endpoint. Enter a publicly reachable
https://URL; plain HTTP is not accepted. - Choose eventsSelect the event types this endpoint should receive. Subscribing only to what you need keeps your handler simple and your logs quiet.
- Copy the signing secretEach endpoint gets its own secret, starting with
whsec_. Store it in your secret manager; you'll use it to verify every request. - Send a test eventChoose Send test to deliver a sample payload and see your endpoint's response code and body in the delivery log.
Endpoints can also be managed programmatically with the Webhooks API.
Event types#
| Event | Sent when |
|---|---|
visitor.qualified | A visitor's score crosses the threshold for the first time |
visitor.score_changed | A score changes by 10 points or more (configurable per endpoint) |
visitor.identified | An anonymous visitor is linked to an email address |
visitor.merged | Two visitor profiles are merged into one |
lead.assigned | A routing rule assigns an owner, or an owner is changed |
lead.status_changed | A lead moves between statuses, such as ready → contacted |
rule.published | A scoring or routing rule is published or restored |
Payload format#
Every event is sent as an HTTP POST with a JSON body and the same envelope: a unique id, the event type, a created_at timestamp and a data object whose shape depends on the type.
{
"id": "evt_01J8Z6Q3T9M2",
"type": "visitor.qualified",
"created_at": "2026-08-24T09:41:12Z",
"workspace_id": "wsp_4Hk29",
"data": {
"visitor": {
"id": "vis_7c1e02",
"email": "dana@northwind.example",
"name": "Dana Whitfield",
"score": 94,
"status": "ready",
"account": { "name": "Northwind Supply", "domain": "northwind.example" },
"top_signals": [
{ "rule": "Pricing page", "points": 36 },
{ "rule": "Came back", "points": 18 },
{ "rule": "Integration docs", "points": 11 }
],
"profile_url": "https://app.aurora.io/v/vis_7c1e02"
},
"threshold": 60
}
}| Header | Description |
|---|---|
Aurora-Signature | Timestamp and HMAC signature, used to verify the request |
Aurora-Event-Id | Same as id in the body; use it for idempotency |
Aurora-Delivery-Attempt | 1 for the first attempt, incremented on each retry |
User-Agent | Aurora-Webhooks/2.0 |
Verifying signatures#
Always verify that a request came from Aurora before acting on it. The Aurora-Signature header looks like t=1724492472,v1=5f2b…. To verify it:
- Split the header into the timestamp
tand the signaturev1. - Build the signed string: the timestamp, a period, and the raw request body.
- Compute an HMAC-SHA256 of that string with your endpoint secret, hex-encoded.
- Compare it to
v1with a constant-time comparison, and reject timestamps older than five minutes.
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.AURORA_WEBHOOK_SECRET; // whsec_...
// Use the raw body: re-serialized JSON will not match the signature.
app.post("/webhooks/aurora", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("Aurora-Signature") || "";
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const signed = `${parts.t}.${req.body.toString("utf8")}`;
const expected = crypto.createHmac("sha256", SECRET).update(signed).digest("hex");
const valid =
parts.v1 &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)) &&
Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // reject replays older than 5 min
if (!valid) return res.status(400).send("Invalid signature");
const event = JSON.parse(req.body.toString("utf8"));
// Acknowledge fast, then process asynchronously (queue, background job, etc.).
res.sendStatus(200);
handleEvent(event);
});Parse the JSON only after verifying the signature, and compute the signature over the exact bytes you received. Most verification failures come from frameworks that parse and re-serialize the body first.
Retries and delivery#
Aurora considers a delivery successful when your endpoint returns any 2xx status within 10 seconds. Anything else — a timeout, a connection error, a 4xx or 5xx — is retried with exponential backoff:
| Attempt | Delay after previous attempt |
|---|---|
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 6 hours |
| 7–8 | 12 hours |
After eight failed attempts over roughly 32 hours, the event is marked failed and can be redelivered manually from the delivery log for up to 30 days. If an endpoint fails every delivery for three consecutive days, it's disabled automatically and workspace admins are notified by email.
Ordering and idempotency#
Events are delivered at least once and usually in order, but neither duplicates nor out-of-order delivery can be ruled out — especially during retries. Design your handler so that:
- Processing the same
Aurora-Event-Idtwice has no additional effect. Store processed IDs for at least seven days. - Stale updates don't overwrite newer ones. Compare
created_atwith the last event you applied for the same visitor. - The handler returns
200quickly and does slow work (API calls, database writes) in a background job.
Testing locally#
To receive webhooks on your laptop, expose a local port with a tunneling tool such as ngrok or cloudflared and register the tunnel URL as a separate endpoint. Keep test endpoints subscribed only to the events you're working on, and delete them when you're done. Every delivery, including the request body and your response, is visible in Settings → Webhooks → Delivery log for 30 days.
Security checklist#
- Verify every signature and reject old timestamps.
- Use a separate secret per endpoint and rotate it from the endpoint page if it's ever exposed. Old secrets keep working for 24 hours after rotation so you can deploy without downtime.
- If you allowlist inbound IPs, use the published ranges at
https://aurora.io/ips.json, which differ for EU and US regions. - Never log full payloads in systems that don't need personal data.