Skip to content

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.