Destinations
Webhook
Receive events on your own HTTPS endpoint, signed
The webhook destination POSTs a JSON array of events to the URL you give
it. Answer 2xx within 15 seconds; anything else is retried as described
in Destinations.
Payload
{
"schema": 1,
"project_uuid": "…",
"events": [
{
"uuid": "…",
"type": "track",
"name": "purchase",
"user_uuid": "…",
"custom_id": "user-42",
"created_at": "2026-09-21T21:15:33.856Z",
"properties": { "amount": "9.99" },
"traits": { "email": "[email protected]" },
"context": { "ip": "203.0.113.9", "user_agent": "…", "sdk": "iforevents-js/0.3.0" }
}
]
}traits is present on identify events only. Property values are
strings, as stored.
Signature
When the destination has a signing secret, every request carries
X-IForevents-Signature: t=<unix seconds>,v1=<hex>
X-IForevents-Destination: <destination uuid>where v1 = HMAC-SHA256(secret, "<t>." + body). Recompute it over the raw
body and compare in constant time; reject timestamps older than five
minutes to defeat replays.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, header, rawBody) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"));
}Idempotency
Retries carry the same event uuid. Store it and ignore a second arrival.