Skip to main content

Webhooks Reference

Complete reference for PDFCanon webhook events, payload schemas, and HMAC signature verification.

Event types​

EventDescription
normalization.successA normalization job completed successfully
normalization.failureA normalization job failed with a permanent error
normalization.rejectedA normalization job was rejected by policy (e.g. signed PDF with reject policy)

Payload schema​

All webhook events share a common envelope:

{
"event": "normalization.success",
"webhookId": "wh_01jk...",
"timestamp": "2026-01-15T12:35:00Z",
"apiVersion": "2026-01-01",
"data": { ... }
}

normalization.success payload​

{
"event": "normalization.success",
"webhookId": "wh_01jk...",
"timestamp": "2026-01-15T12:35:00Z",
"apiVersion": "2026-01-01",
"data": {
"submissionId": "sub_01jk...",
"status": "SUCCESS",
"processingTimeMs": 342,
"outputHash": "sha256:ddeeff...",
"outputSizeBytes": 98304,
"downloadUrl": "https://api.pdfcanon.com/api/artifacts/ddeeff...",
"warnings": []
}
}

normalization.failure payload​

{
"event": "normalization.failure",
"webhookId": "wh_01jk...",
"timestamp": "2026-01-15T12:35:00Z",
"apiVersion": "2026-01-01",
"data": {
"submissionId": "sub_01jk...",
"status": "FAILED",
"processingTimeMs": 89,
"error": {
"type": "CORRUPT_UNRECOVERABLE",
"message": "PDF structure is too corrupted to repair"
}
}
}

normalization.rejected payload​

{
"event": "normalization.rejected",
"webhookId": "wh_01jk...",
"timestamp": "2026-01-15T12:35:01Z",
"apiVersion": "2026-01-01",
"data": {
"submissionId": "sub_01jk...",
"status": "REJECTED",
"processingTimeMs": 12,
"error": {
"type": "SIGNED_PDF",
"message": "PDF contains digital signatures which would be invalidated by normalization."
}
}
}

HMAC signature verification​

PDFCanon signs every webhook request with HMAC-SHA256 using your webhook secret.

The signature is sent in the X-PDFCanon-Signature header as a lowercase hex string.

Verification​

To verify: compute HMAC-SHA256(secret, rawRequestBody) and compare with the header value using a constant-time comparison.

// Node.js
const crypto = require('crypto');

function verifySignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
);
}
# Python
import hmac, hashlib

def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
// C#
using System.Security.Cryptography;
using System.Text;

bool VerifySignature(string rawBody, string signature, string secret)
{
var key = Encoding.UTF8.GetBytes(secret);
var body = Encoding.UTF8.GetBytes(rawBody);
var expected = Convert.ToHexString(HMACSHA256.HashData(key, body)).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(signature));
}

Retry policy​

AttemptDelay
1 (initial)—
230 seconds
32 minutes
410 minutes
530 minutes

After 5 failed attempts, the webhook is marked as failed and no further retries are made. You can manually re-trigger delivery from the portal.

Headers​

HeaderDescription
Content-Typeapplication/json
X-PDFCanon-SignatureHMAC-SHA256 signature (hex)
X-PDFCanon-EventEvent type (e.g. normalization.success)
X-PDFCanon-Webhook-IdWebhook delivery ID
X-PDFCanon-Api-VersionAPI version string