> ## Documentation Index
> Fetch the complete documentation index at: https://synapse-docs.apart.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Verify signatures and operate at-least-once, replayable Synapse event delivery.

# Signed, replayable webhooks

Receive tenant events with timestamped HMAC verification, durable event
deduplication, secret rotation, delivery history, and operator replay.

<Steps>
  <Step title="Register a narrow endpoint">
    Choose only the event types your integration needs and persist the one-time
    signing secret in your server secret store.

    ```typescript theme={null}
    const endpoint = await synapse.webhooks.create(
      organizationId,
      appId,
      environmentId,
      {
        url: "https://app.example/webhooks/synapse",
        eventTypes: ["evaluation.completed", "alignment.drifted"],
      },
      { idempotencyKey: persistedRequestId },
    );

    // Store endpoint.secret now. It is shown only once.
    await secretStore.put("synapse-webhook-current", endpoint.secret);
    ```
  </Step>

  <Step title="Verify the raw bytes">
    Read the exact request body once and verify it before parsing JSON. Allow
    both the current and previous secret during a controlled rotation window.

    ```typescript theme={null}
    import { verifyWebhookSignature } from "@apart-ai/synapse/webhooks";

    export async function handleSynapseWebhook(request: Request) {
      const signature = request.headers.get("X-Synapse-Signature") ?? "";
      const eventId = request.headers.get("X-Synapse-Event-Id") ?? "";
      if (!signature || !eventId) {
        return new Response("Missing required webhook headers", { status: 400 });
      }
      const rawBody = await request.text();

      await verifyWebhookSignature(
        rawBody,
        signature,
        [
          process.env.SYNAPSE_WEBHOOK_SECRET!,
          process.env.SYNAPSE_PREVIOUS_SECRET!,
        ],
        { toleranceSeconds: 300 },
      );

      if (await wasAlreadyProcessed(eventId)) {
        return new Response(null, { status: 204 });
      }

      const event = JSON.parse(rawBody);
      await processEventAndRememberId(event, eventId);
      return new Response(null, { status: 204 });
    }
    ```
  </Step>

  <Step title="Deduplicate and acknowledge">
    Commit your business change and `X-Synapse-Event-Id` in one transaction.
    Return a `2xx` only after both succeed.
  </Step>

  <Step title="Operate retries">
    Delivery is at least once and may be reordered. Inspect delivery history,
    replay failures after correction, and alert on dead-lettered or aging
    deliveries.
  </Step>
</Steps>

<Warning>
  Register HTTPS endpoints you control. Do not reflect payloads, signatures,
  secrets, participant identifiers, or report content into logs. Treat every
  delivery body as sensitive customer data.
</Warning>

## Delivery headers

| Header                 | Purpose                                                        |
| ---------------------- | -------------------------------------------------------------- |
| `X-Synapse-Signature`  | Timestamped HMAC signature over the exact raw body             |
| `X-Synapse-Event-Id`   | Stable ID for durable duplicate suppression                    |
| `X-Synapse-Event-Type` | Routing hint; verify the payload and endpoint subscription too |
