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