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