# Guide to Yousign Webhooks: Features and Best Practices

Yousign webhooks tell your systems how a signing process is going: a signature request is activated, a signer opens the link, signs or declines, an approver rejects, a request expires, or an identity check or electronic seal finishes. If you send contracts, mandates or onboarding documents through Yousign, the European e-signature platform, webhooks are how that progress reaches your CRM, document storage and back-office systems as it happens.

This guide covers how Yousign webhook subscriptions work, how deliveries are signed, the events you can subscribe to, the strict response window and retry schedule, and the practices that keep a Yousign receiver reliable.

## What are Yousign webhooks?

A Yousign webhook subscription names the events you want, the environment they come from (sandbox or production) and the HTTPS endpoint to send them to. When a subscribed event happens, Yousign sends a JSON `POST` to that endpoint.

Yousign's developer documentation now lives under the Youtrust brand, at developers.youtrust.com, and its pages say "Youtrust" throughout. The wire format has not changed: the headers still start with `x-yousign-`, and the user agent is still `Yousign Webhook Bot`.

Every delivery is signed. Yousign computes an HMAC-SHA256 of the raw body with the subscription's secret key, encodes it as hex, prefixes it with `sha256=` and sends it in `X-Yousign-Signature-256`. A separate `X-Yousign-Issued-At` header carries the send time, but it is not part of the signature.

The detail that matters most is the response window. Yousign expects a `2xx` within **1 second** on the first attempt, so a handler that does real work before responding will fail, and its events will arrive later as retries.

## Yousign webhook features

| Feature | Details |
| --- | --- |
| Configuration | The app (**API > Webhooks**) or the webhook subscription API |
| Subscription | Chosen events, or `*` for all events that exist at creation or update |
| Environments | Sandbox or production, chosen per subscription |
| Filters | `scope` (`app`, `public_api` or `*`) and `workspace` (IDs or `*`) |
| Quotas | 10 subscriptions on PLUS, 20 on PRO, 50 on SCALE |
| Signature header | `X-Yousign-Signature-256: sha256=<hex>` |
| Scheme | HMAC-SHA256, lowercase hex, `sha256=` prefix |
| Signed content | The raw request body |
| Secret | One per subscription, shown when you view it in the app |
| Other headers | `X-Yousign-Retry` (0 on first delivery), `X-Yousign-Issued-At` (not signed) |
| Response window | 1 second on the first attempt, 10 seconds on retries |
| Success | A `2xx` or `3xx` status, with no more than two redirects |
| Retries | Up to 8 when `auto_retry` is on, spread over about 4 days |
| Endpoint rules | HTTPS only, no self-signed certificates, no private networks |
| Source IPs | Published, see below |

If your endpoint is behind a firewall, Yousign lists the addresses to allow: `57.130.41.144/28`, `51.38.96.112/28`, `5.39.7.128/28`, `52.143.162.31` and `51.103.81.166`.

## Common events

Yousign's webhook overview groups its events by product, and its API reference lists a few more that the overview doesn't yet include. Here are the main families:

| Area | Events | Fires when |
| --- | --- | --- |
| Signature requests | `signature_request.activated`, `.done`, `.declined`, `.expired`, `.canceled`, `.approved`, `.rejected`, `.reactivated`, `.deleted`, `.permanently_deleted`, `.reminder_executed`, `.automatic_reminder_executed`, `.paused`, `.resumed` | A request moves between `draft`, `approval`, `ongoing` and its final states, is deleted or restored, or a reminder is sent |
| Signers | `signer.notified`, `.link_opened`, `.done`, `.declined`, `.error`, `.sender_contacted`, `.notification_delivery_failed`, `.identification_succeeded`, `.identification_failed`, `.identification_blocked`, `.identification_expired`, `.identity_saved` | A signer is invited, opens the link, signs, declines or hits an error, or passes or fails identification for AES and QES signatures |
| Approvers | `approver.notified`, `.approved`, `.rejected`, `.notification_delivery_failed` | An approver is invited, approves or rejects, or their first email bounces |
| Document verification | `document_analysis.done`, `verification.identity_document.done`, `verification.identity_video.done`, `verification.identity_video.revised`, `verification.proof_of_address.done`, `verification.company.done`, `verification.bank_account.done`, `verification.bank_account_lookup.done`, `verification.watchlist.done`, `verification.signature_seal.done` | A verification has been processed or revised |
| Electronic seal | `electronic_seal.done`, `electronic_seal.error` | A seal was applied, or failed |
| Workflows | `workflow_session.started`, `.blocked`, `.done`, `workflow_action_group.started`, `.blocked`, `.done`, `applicant.notified`, `.processed`, `.session_blocked` | A workflow session or action group changes status, or an applicant is invited, finishes or runs out of retries |
| Other | `contact.created`, `user.completed`, `monitoring.natural_person.activated`, `.inconclusive`, `.canceled`, `.updated` | A contact is created, a user completes their invitation, or ongoing monitoring of a person changes |

The events most integrations start with are `signature_request.done` (all required signers have signed), `signer.done`, `signer.declined` and `signature_request.expired`. One event, `verification.bank_account_connection.done`, can no longer be subscribed to but still reaches older subscriptions.

Every delivery shares one envelope:

```json
{
  "event_id": "b6c63685-c556-4a30-8fe9-b6f2b187d936",
  "event_name": "signature_request.activated",
  "event_time": "1670855889",
  "subscription_id": "webhook-subscription-id",
  "subscription_description": "My webhook for signed documents",
  "sandbox": false,
  "data": {
    "signature_request": {
      "id": "xxx-xxx",
      "status": "approval"
    }
  }
}

```

`event_id` is globally unique, which makes it the deduplication key. `event_time` is when the event occurred, as a Unix timestamp in a string, and it stays the same across retries. `sandbox` tells you which environment the event came from. `data` depends on the event, and Yousign's API reference has a page per event describing it. Yousign's own example cuts `data` short, so treat the object above as a sketch of the envelope.

## Setting up Yousign webhooks

1. In the Yousign app, go to **API > Webhooks** and click **Create a webhook**.
2. Enter your HTTPS endpoint URL and an optional description.
3. Choose **sandbox** or **production**, then select the events to subscribe to.
4. Click **Create a webhook**, open the new subscription, and copy its secret key into your server's configuration, for example as `YOUSIGN_WEBHOOK_SECRET`.

To create subscriptions from code, use the webhook endpoints of the Public API v3. The same attributes are available there, including `auto_retry`. Set `auto_retry` to `true`: without it, Yousign only retries once, and only when the first attempt timed out.

If your API account is in trial mode, you can only create sandbox subscriptions, and only through the app. Changes to a subscription apply only to events triggered afterwards, and deleting one stops all deliveries straight away, including retries already in progress.

If you use Hookdeck, create a Yousign source, set the subscription's secret key as its **Webhook Signing Secret**, and use the source URL as the subscription endpoint. Yousign signs every delivery, so turn verification on.

## Securing Yousign webhooks

Compute an HMAC-SHA256 of the raw request body with the secret key, encode it as hex, prepend `sha256=`, and compare the result with `X-Yousign-Signature-256` in constant time. Yousign's documentation warns specifically against hashing a parsed or re-formatted body.

```javascript
const crypto = require('crypto');
const express = require('express');

function verifyYousignSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;

  const expected =
    'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');

  const received = Buffer.from(signatureHeader.trim(), 'utf8');
  const computed = Buffer.from(expected, 'utf8');
  return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}

const app = express();

app.post('/webhooks/yousign', express.raw({ type: '*/*' }), (req, res) => {
  const secret = process.env.YOUSIGN_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500); // fail closed

  if (!verifyYousignSignature(req.body, req.get('x-yousign-signature-256'), secret)) {
    return res.sendStatus(401);
  }

  // Answer inside 1 second: store the raw event, then process it asynchronously.
  const event = JSON.parse(req.body.toString('utf8'));
  // Deduplicate on event.event_id, route on event.event_name.
  res.sendStatus(200);
});

```

Yousign's own Node sample calls `timingSafeEqual` directly, which throws when the two values differ in length. The length check above turns that into a plain `false`.

The same check in Python:

```python
import hashlib
import hmac

def verify_yousign_signature(raw_body: bytes, signature_header, secret: str) -> bool:
    if not signature_header or not secret:
        return False
    digest = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    expected = f"sha256={digest}"
    return hmac.compare_digest(signature_header.strip().encode("utf-8"), expected.encode("utf-8"))

# In FastAPI: raw_body = await request.body()
# verify_yousign_signature(raw_body, request.headers.get("x-yousign-signature-256"),
#                          os.environ["YOUSIGN_WEBHOOK_SECRET"])

```

To check your verifier, use the secret `yousign-test-secret` and this exact body:

```
{"event_id":"b6c63685-c556-4a30-8fe9-b6f2b187d936","event_name":"signature_request.done"}

```

The header should be:

```
sha256=e3af9225d895569b02f528f43ad8c0dcc69d051982805fa4173b69dd173cdb2e

```

`X-Yousign-Issued-At` is not covered by the signature, so an attacker who replays a captured request can set it to anything. Don't use it as a freshness check. Rely on deduplicating `event_id` instead.

> **Make Yousign webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) verifies the `X-Yousign-Signature-256` header, answers Yousign well inside its 1-second window, and durably queues every signature request and signer event before your service sees it.

## Yousign webhook limitations and pain points

### A 1-second response window

**The Problem:** Deliveries fail even though your endpoint is up and returns `200`.

**Why It Happens:** Yousign gives the first attempt 1 second. A response that arrives later counts as a failure even if it is a `2xx`. Cold starts, a slow database write or a call to download the signed document are enough to miss it.

**Workarounds:**

* Verify, store the raw event and respond, with nothing else on the request path.
* Keep the endpoint warm if it runs on serverless infrastructure.
* Download documents and update records from a background job.

**How Hookdeck Can Help:** Hookdeck acknowledges Yousign as soon as the request arrives, then delivers to your service with its own timeout and retry policy, so the 1-second limit no longer applies to your code.

### Retries depend on a subscription setting

**The Problem:** A failed delivery is never retried.

**Why It Happens:** Automatic retries only happen when the subscription's `auto_retry` is `true`. With it off, Yousign makes one extra attempt after a first-attempt timeout and none after an error status. With it on, it retries up to 8 times, after 2 minutes, 6 minutes, 30 minutes, 1 hour, 5 hours, 18 hours, 1 day and 2 days.

**Workarounds:**

* Set `auto_retry` to `true` on every subscription.
* Use `X-Yousign-Retry` to monitor how often first attempts fail.
* Reconcile important signature requests through the API after an outage.

**How Hookdeck Can Help:** Once Hookdeck has accepted an event, it retries delivery to your service on a schedule you choose and lets you replay failures, independent of Yousign's `auto_retry` setting.

### Suspended endpoints

**The Problem:** Events stop arriving for a while after a run of failures.

**Why It Happens:** Yousign says it may temporarily suspend deliveries to an endpoint whose retries keep failing, queueing them until the endpoint is stable again. It does not publish the thresholds.

**Workarounds:**

* Treat repeated `X-Yousign-Retry` values above `0` as an alert, before suspension happens.
* Fix failing endpoints quickly, and expect a backlog once deliveries resume.

**How Hookdeck Can Help:** Hookdeck keeps answering Yousign while your service is down, so your outages don't build up the failure history that leads to suspension.

### An unsigned timestamp

**The Problem:** There is no reliable way to reject an old, replayed request.

**Why It Happens:** Only the body is signed. `X-Yousign-Issued-At` sits outside the signature, and `event_time` inside the body is the event time, which stays the same through retries, so neither bounds a replay.

**Workarounds:**

* Deduplicate on `event_id`.
* Keep the endpoint on HTTPS and, where your network allows, restrict it to Yousign's published IP ranges.

**How Hookdeck Can Help:** Hookdeck's deduplication can drop repeated deliveries based on `event_id` before they reach your service.

## Best practices

### Verify against the raw body

The HMAC covers the exact bytes Yousign sent. In Express, use `express.raw({ type: '*/*' })` and parse after verification. In Next.js, read `await request.text()`. In FastAPI, read `await request.body()`.

### Deduplicate on `event_id`

Yousign says you may receive the same webhook more than once, through retries or network issues. Store `event_id` before processing and skip anything you have already handled. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Route on `event_name`, and check `sandbox`

Switch on `event_name`. If one endpoint receives both environments, check `sandbox` before acting, so a test signature never updates a production record.

### Subscribe to what you handle

Subscribing with `*` covers every event that exists when you create or update the subscription, not events added later. List the events you actually handle, and review the list when Yousign adds new ones.

### Acknowledge fast and process asynchronously

With a 1-second window, this is a requirement rather than a recommendation. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Yousign signs every webhook with `sha256=` and a hex HMAC-SHA256 of the raw body in `X-Yousign-Signature-256`, keyed with the subscription's secret. Verify against the raw bytes, deduplicate on `event_id`, route on `event_name`, turn `auto_retry` on, and respond within 1 second, because a slow response counts as a failure even when it succeeds.

[Hookdeck Event Gateway](https://hookdeck.com) sits in front of your receiver, checks the Yousign signature, answers inside the 1-second window, and holds each event until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Yousign webhooks reliably in minutes.