Handling Webhooks

This page walks you through how to receive, validate, and process webhook messages securely and efficiently within your application.

Once you’ve set up a webhook subscription, Yousign will send HTTP POST requests to your configured endpoint whenever one of your subscribed events occurs.

This page explains how to handle these requests securely and reliably.


📦 Webhook Message Structure

Each webhook message has two parts:

1. HTTP Headers

HeaderDescription
Content-TypeAlways application/json
X-Yousign-Signature-256HMAC SHA-256 signature to verify payload authenticity
X-Yousign-RetryRetry count (0 for first delivery)
X-Yousign-Issued-AtTimestamp when the webhook was sent
User-AgentAlways Yousign Webhook Bot

2. JSON Payload

{
  "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",
      ...
    }
  }
}
AttributeValue
event_idA unique identifier for this webhook event.
event_nameThe name of the webhook event that was triggered.
event_timeThe timestamp (Unix epoch format) when the event occurred. It will stay constant even through retries.
subscription_idThe unique identifier of the webhook subscription that received this event.
subscription_descriptionDescription of the webhook subscription.
sandboxA boolean indicating whether this event was triggered in a sandbox environment (true) or in production (false).
dataContains detailed information about the resources that triggered this event. The data varies depending on the event. Refer to the API Reference .

🔐 Verifying Webhook Authenticity

To ensure the webhook is really from Yousign and hasn’t been tampered with, you must validate its signature.

Step-by-step:

  1. Get the webhook secret key

    • Found in the Yousign App when viewing your webhook subscription.

  2. Compute a SHA-256 HMAC hash of the raw payload using your secret key.

  3. Prefix the hash with sha256=.

  4. Compare it with the value of the X-Yousign-Signature-256 header.

🚧

Make sure to hash the raw request body—not a parsed or formatted version.

Examples:

// /!\ getallheaders() function does not work on all web servers.
$headers = getallheaders();
$expectedSignature = $headers['X-Yousign-Signature-256'] ?? null;

$payload = file_get_contents('php://input'); // This is an example, you should get the content from an HTTP request
$secret = 'SECRET_KEY_OF_YOUR_WEBHOOK';
$digest = hash_hmac('sha256', $payload, $secret);
$computedSignature = "sha256=" . $digest;

$doSignaturesMatch = hash_equals($expectedSignature, $computedSignature);
const express = require("express");
const crypto = require("crypto");
const https = require("https");

const app = express();
const secret = 'YOUR_WEBHOOK_SECRET_KEY';
const port = 3000;

// SSL Certification
const options = {
  key: 'PRIVATE_KEY',
  cert: 'CERTIFICATE',
};

/**
 * If you are not using Express, you need to find a way to get the raw request body and
 * encoding it.
 * You can for example, use the `raw-body` middleware https://www.npmjs.com/package/raw-body
 */
app.use(
  express.json({
    verify: (req, res, buf, encoding) => {
      if (buf && buf.length) {
        req.rawBody = buf.toString(encoding || "utf-8");
      }
    },
  })
)

app.post('/hook', (req, res) => {
  const signature = Buffer.from(req.get('X-Yousign-Signature-256') || '', 'utf8');
  const hmac = crypto.createHmac('sha256', secret);
  const computedSignature = Buffer.from(`${'sha256' + '='}${hmac.update(req.rawBody).digest('hex')}`, 'utf8');
  
  const isSignatureMatch = crypto.timingSafeEqual(signature, computedSignature)
})

https.createServer(options, app).listen(port);
import hmac
import hashlib

SECRET = "SECRET_KEY_OF_YOUR_WEBHOOK"
signature = "SIGNATURE_FROM_REQUEST_HEADERS_RECEIVED"
data = "DATA_FROM_REQUEST_PAYLOAD"

digest = hmac.new(SECRET.encode("utf-8"), data.encode("utf-8"), hashlib.sha256).hexdigest()
computed_signature = f"sha256={digest}"
matches = hmac.compare_digest(signature, computed_signature)
public class Main {
    public static void main(String[] args) {
        String payload = "..."
        String signature = "sha256=0af379c7522c538c3f26a4f4399e55d7a1ed928420735a4538c73232587be9d6";
        String SECRET = "2977fd4b627d28c6e54501b4e8a667ae";

        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
        sha256_HMAC.init(secret_key);

        byte[] hash = sha256_HMAC.doFinal(payload.getBytes());
        StringBuilder result = new StringBuilder();

        for (byte b : hash) {
            result.append(String.format("%02x", b));
        }

        String computedSignature = "sha256=" + result.toString();

        boolean matches = signature.equals(computedSignature);
    }
}

📥 Best Practices for Processing Webhooks

Yousign webhooks are designed to be fast and reliable. To make the most of them:

Return a 2XX status quickly

Your server must return a 2xx HTTP status code within 1 second. If not:

  • The delivery is considered failed
  • The webhook will be retried

Even if you don’t want to process the event immediately, acknowledge it first, then process it asynchronously.

Be idempotent

You may receive the same webhook multiple times (due to retries or network issues).

Use the event_id (which is globally unique) to deduplicate events.

Accept JSON

Your listener must accept application/json requests and handle variable payload structures depending on the event.

Handle retries gracefully

  • Use the X-Yousign-Retry header to track retry attempts
  • Always check the event_id to avoid duplicate processing

See Failure & Retry Policy.

🚧 Timeouts and Failures

  • If you respond after 1s on the first delivery attempt, it’s considered a timeout.
  • Retry attempts are allowed up to 10s, but don’t rely on that grace period.
  • Never assume a webhook was delivered just because your system is up—make sure to log them and monitor failures.

🧪 Testing Locally

For development, you can use tools like https://webhook.site to simulate your webhook listener:

  1. Create a webhook subscription with your temporary listener URL.
  2. Trigger a test Signature Request (in sandbox).
  3. View the payload and headers in your tool.

✅ Use sandbox mode to simulate events safely during development.

⚠️

Remember to delete your webhook subscription after testing

Temporary endpoints like webhook.site expire and cause unnecessary failed calls if left active.


🔒 Security and IP Whitelisting

  • HTTPS is required for all webhook endpoints.
  • Self-signed certificates are not allowed.
  • Private networks are not supported (e.g. 192.168.x.x, 10.x.x.x, 172.16.x.x).
  • If needed, allow incoming traffic from:
    • CIDR 5.39.7.128/28 (from IP 5.39.7.128 to IP 5.39.7.143)
    • 52.143.162.31
    • 51.103.81.166