Skip to main content
A webhook sends an HTTPS POST request to your server when something happens in a sajn workspace. Use webhooks instead of polling to do things like the following:
  • Download the sealed PDF as soon as a document is completed.
  • Keep the status of an agreement in your CRM or ERP in step with sajn.
  • Start your own onboarding flow when a party signs, or follow up with a party whose invitation bounced.
  • Feed security events, such as downloads and role changes, into a SIEM.
Webhooks belong to a workspace. An endpoint receives the events of the workspace it was created in, and you manage it with that workspace’s API key or in its settings. Webhooks are part of API access, which requires the Team plan or higher; a sandbox organization always has it. sajn signs and sends webhooks according to Standard Webhooks, an open specification, so the official Standard Webhooks libraries verify them without sajn-specific code.

Set up an endpoint

1

Expose an HTTPS endpoint

Add a route to your server that accepts a POST request with a JSON body and returns a 2xx status code. sajn delivers only to public HTTPS URLs. To receive deliveries on your own machine, see Test webhooks locally.
2

Create the webhook

Choose the events you want and point them at your URL.
  1. In the dashboard, go to Inställningar > Utvecklare > Webhooks.
  2. Click Skapa webhook (Create webhook).
  3. In Webhook URL, enter your endpoint’s URL.
  4. In Händelser (Events), select the events to subscribe to.
  5. Click Skapa webhook.
3

Store the signing secret

The API response contains secret, a whsec_ key that sajn signs every delivery with. sajn returns it only when you create the webhook and when you rotate the secret, so store it now, for example as the SAJN_WEBHOOK_SECRET environment variable.
4

Verify each delivery

Check the webhook-signature header against the raw request body before you trust a delivery. Verify webhook signatures shows how, with a Standard Webhooks library or with your own code.
5

Send a test event

Call POST /api/v1/webhooks/{id}/test to send a signed webhook.test event to your endpoint. Each attempt appears with its status code and response in Inställningar > Utvecklare > Loggar, and through GET /api/v1/webhooks/{id}/deliveries.

The event

Every delivery is a POST request whose JSON body is an event. It’s the same object that GET /api/v1/events/{id} returns, so the code that handles a webhook also handles an event that you read from the API:
data.object in this example is shortened. The event has the following fields:

Headers

Each delivery carries the following headers:
An endpoint on API version 2026-09 keeps the earlier contract: the body { event, payload, createdAt, webhookEndpoint, apiVersion } with uppercase event names such as DOCUMENT_COMPLETED, and the X-Sajn-Signature, X-Sajn-Delivery, X-Sajn-Environment, and X-Sajn-Secret headers instead of the Standard Webhooks headers. To move an endpoint, see Upgrading to 2026-10.

How delivery behaves

Build your endpoint around five rules. Verify, then respond fast. Check the signature, return a 2xx status code within 30 seconds, and then do the work. sajn treats a slower response as a failed attempt. We recommend that you put the event on a queue and process it from there. Expect duplicates. Delivery is at least once. A retry after a timeout, a replay, and a delivery to a second endpoint all carry the same event id. Store the IDs that you’ve processed and skip the ones you’ve seen. Don’t rely on order. Each delivery is independent, and a retried delivery can arrive after a later event. For example, document.party.signed can arrive after document.completed for the same document. Compare createdAt values, or read the current state from the API, before you overwrite newer data with older data. Treat data.object as a snapshot. In a delivery, it’s the resource as it was when the event happened, and a retry carries the same snapshot. Read the resource from the API when you need its current state. GET /api/v1/events returns the same snapshot, except for an event that no 2026-10 endpoint subscribed to when it happened: sajn builds that event’s data.object from the resource’s current state. Ignore fields you don’t know. sajn adds fields to events, and adds event types, without a new API version, so your endpoint must accept keys that it doesn’t recognize. Most JSON parsers do. Strict ones don’t: check zod schemas with .strict(), Pydantic models with extra="forbid", JSON Schema with additionalProperties: false, Go’s DisallowUnknownFields(), and Jackson, which turns on FAIL_ON_UNKNOWN_PROPERTIES by default. sajn never removes or retypes an existing field without a new API version. sajn retries a failed attempt for about three days. A 410 Gone response stops the retries and pauses the endpoint. For the schedule, see Delivery and retries. If your endpoint missed events, Replay and reconcile events shows how to catch up.

Next steps

Webhook events

Every event type, when it fires, and the data it carries.

Verify signatures

Reject deliveries that didn’t come from sajn.

Manage endpoints

Create, update, rotate, reactivate, and delete webhooks.

Test webhooks locally

Receive real deliveries on your own machine.