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
Header | Description |
---|---|
Content-Type | Always application/json |
X-Yousign-Signature-256 | HMAC SHA-256 signature to verify payload authenticity |
X-Yousign-Retry | Retry count (0 for first delivery) |
X-Yousign-Issued-At | Timestamp when the webhook was sent |
User-Agent | Always 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",
...
}
}
}
Attribute | Value |
---|---|
event_id | A unique identifier for this webhook event. |
event_name | The name of the webhook event that was triggered. |
event_time | The timestamp (Unix epoch format) when the event occurred. It will stay constant even through retries. |
subscription_id | The unique identifier of the webhook subscription that received this event. |
subscription_description | Description of the webhook subscription. |
sandbox | A boolean indicating whether this event was triggered in a sandbox environment (true) or in production (false). |
data | Contains 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:
-
Get the webhook secret key
- Found in the Yousign App when viewing your webhook subscription.
-
Compute a SHA-256 HMAC hash of the raw payload using your secret key.
-
Prefix the hash
with sha256=
. -
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
🚧 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:
- Create a webhook subscription with your temporary listener URL.
- Trigger a test Signature Request (in sandbox).
- 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
Updated 10 days ago