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.
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.
- Dashboard
- API
- In the dashboard, go to Inställningar > Utvecklare > Webhooks.
- Click Skapa webhook (Create webhook).
- In Webhook URL, enter your endpoint’s URL.
- In Händelser (Events), select the events to subscribe to.
- 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 aPOST 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 a2xx 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.

