Webhooks
Webhooks send a signed copy of each event to your server, so entries land in your own database or CRM in real time. Loranj stays the system of record.
Events
| Event | When |
| ----------------- | ----------------------------------------------- |
| entry.created | An entry was accepted |
| entry.flagged | An entry was flagged for review |
| winner.selected | A draw picked a winner |
| entry.deleted | Personal data was erased (delete your copy too) |
Verify the signature
import { constructWebhookEvent } from "@loranj/sdk/server";
export async function POST(request: Request) {
const body = await request.text(); // the raw body, before JSON parsing
try {
const event = await constructWebhookEvent(
body,
request.headers.get("loranj-signature"),
process.env.LORANJ_WEBHOOK_SECRET!,
);
// Delivery is at-least-once: skip events whose event.id you have already stored.
if (event.type === "entry.created") {
// save event.data
}
return new Response(null, { status: 204 });
} catch {
return new Response("Invalid signature", { status: 400 });
}
}
verifyWebhook(body, header, secret) returns true or false instead of throwing.
Signature format
Loranj-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body">. Signatures older than five minutes are rejected, to stop replays. During secret rotation the header can carry two v1 values; either one matching is enough.
Your data responsibilities
The brand is the data controller and your agency is a processor. When you receive entry.deleted, delete the person's details from your copy.