Webhooks

Webhooks

Webhooks allow you to receive real-time notifications when asynchronous PDF generation completes or fails.

Webhooks only fire for asynchronous requests (using Prefer: respond-async header). Synchronous requests return PDFs directly.

Two Webhook Systems

doczap supports two webhook systems for flexibility:

SystemConfigurationFeaturesUse Case
Dashboard SystemDashboard UIHMAC signatures, advanced filtering, retry management, logsRecommended
Legacy SystemRequest bodyBasic retry, custom headersBackward compatibility

Both systems can be used simultaneously.

1. Configure in Dashboard

  1. Go to Webhooks in your dashboard
  2. Click “Add Webhook”
  3. Enter your endpoint URL and name
  4. Select events to subscribe to
  5. Configure scope (organization-wide or specific templates)
  6. Set up filters (optional)
  7. Copy the signing secret for verification

Webhook Filtering

Filter which events trigger your webhook:

FilterDescriptionExample
Test ModeOnly test or production documentsis_test: false
Document SizeMin/max file size in KBmin_size_kb: 100, max_size_kb: 5000
Template PatternRegex pattern for endpoint pathsendpoint_path_pattern: "invoice-*"
Exclude ErrorsSkip specific error codesexclude_errors: ["TEMPLATE_NOT_FOUND"]

2. Webhook Events

EventDescriptionPayload
document.completedPDF generated successfullyDocument data + URL
document.failedPDF generation failedError details

3. Webhook Payload

Success Event (document.completed)

{
  "id": "evt_abc123xyz",
  "type": "document.completed",
  "created": 1640995200,
  "data": {
    "request_id": "req_def456ghi",
    "document_id": "doc_xyz789abc",
    "organization_id": "org_abc123",
    "template_id": "tpl_def456",
    "document_url": "https://cdn.doczap.app/outputs/doc_xyz789abc.pdf",
    "size_bytes": 245632,
    "render_time_ms": 245,
    "created_at": "2024-01-07T12:00:00Z",
    "completed_at": "2024-01-07T12:00:00.245Z",
    "is_test": false
  }
}

Failure Event (document.failed)

{
  "id": "evt_def456xyz",
  "type": "document.failed",
  "created": 1640995200,
  "data": {
    "request_id": "req_ghi789def",
    "document_id": "doc_mno345pqr",
    "organization_id": "org_abc123",
    "template_id": "tpl_def456",
    "error": {
      "code": "RENDER_TIMEOUT",
      "message": "PDF generation timed out after 30 seconds",
      "details": {
        "timeout_ms": 30000,
        "template_size": 1024000
      }
    },
    "created_at": "2024-01-07T12:00:00Z",
    "failed_at": "2024-01-07T12:00:30Z",
    "is_test": false
  }
}

4. Verify Webhook Signatures

const crypto = require('crypto');
 
function verifyWebhookSignature(payload, signature, secret) {
  const timestamp = signature.split(',')[0].split('=')[1];
  const receivedSig = signature.split(',')[1].split('=')[1];
  
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${payload}`)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(receivedSig),
    Buffer.from(expectedSig)
  );
}
 
// Express.js example
app.post('/webhook', express.raw({type: '*/*'}), (req, res) => {
  const signature = req.headers['x-doczap-signature'];
  const isValid = verifyWebhookSignature(
    req.body.toString(),
    signature,
    process.env.WEBHOOK_SECRET
  );
  
  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  
  const event = JSON.parse(req.body);
  
  switch (event.type) {
    case 'document.completed':
      console.log('PDF ready:', event.data.pdf_url);
      break;
    case 'document.failed':
      console.error('PDF failed:', event.data.error);
      break;
  }
  
  res.status(200).send('OK');
});

Legacy System

Include webhook configuration in your request:

{
  "customer": "Acme Corp",
  "amount": 1250.00,
  "webhook": {
    "url": "https://your-app.com/webhooks/doczap",
    "headers": {
      "X-Custom-Auth": "your-secret"
    },
    "retries": 3
  }
}

Legacy webhooks don’t include signatures. Use the new system for production.

Retry Logic

Dashboard System

Configurable retry settings per webhook:

SettingDefaultDescription
Max Retries3Maximum retry attempts (0-5)
Timeout30sRequest timeout (5-60 seconds)
Backoff Multiplier2Exponential backoff factor

Retry delays with default settings:

AttemptBase DelayWith JitterTotal Time
1Immediate0s0s
22s1-3s~2s
34s2-6s~6s
48s4-12s~14s

Legacy System

Fixed exponential backoff:

AttemptDelayTotal Time
1Immediate0s
2~2s2s
3~4s6s
4~8s14s
5~16s30s

Failed webhooks after all retries are logged and can be manually re-sent from the dashboard.

Webhook Requirements

Your endpoint must:

  1. Accept POST requests with JSON payload
  2. Return 2xx status within 30 seconds
  3. Handle duplicates - Use event ID for idempotency
  4. Verify signatures - Prevent replay attacks

Testing Webhooks

1. Use Webhook Testing Tools

# 1. Go to https://webhook.site
# 2. Copy your unique URL
# 3. Use it as your webhook endpoint
 
curl -X POST https://api.doczap.app/api/v1/... \
  -H "Prefer: respond-async" \
  -d '{
    "webhook": {
      "url": "https://webhook.site/your-unique-id"
    }
  }'

2. Test from Dashboard

  1. Configure a webhook in the dashboard
  2. Click “Send Test Event”
  3. Verify your endpoint receives the test payload

Common Issues

Webhook Not Receiving Events

  1. Check async mode - Webhooks only fire for async requests
  2. Verify URL - Must be publicly accessible HTTPS
  3. Check firewall - Allow Doczap IPs (see docs)
  4. Review logs - Check webhook logs in dashboard

Signature Verification Failing

  1. Use raw body - Don’t parse JSON before verifying
  2. Check header name - X-Doczap-Signature (case-insensitive)
  3. Verify secret - Must match dashboard exactly
  4. Handle encoding - UTF-8 encoding required

Duplicate Events

Implement idempotency:

const processedEvents = new Set();
 
function handleWebhook(event) {
  if (processedEvents.has(event.id)) {
    return; // Already processed
  }
  
  processedEvents.add(event.id);
  // Process event...
}

Webhook Logs

View detailed logs for all webhook attempts in the dashboard:

  • Request/response details
  • HTTP status codes
  • Response time
  • Retry attempts
  • Error messages

Logs are retained for:

  • Free plan: 2 days
  • Pro plan: 90 days
  • Scale plan: 365 days

Best Practices

  1. Return quickly - Process asynchronously if needed
  2. Log everything - Include event IDs and timestamps
  3. Monitor failures - Set up alerts for webhook errors
  4. Use signatures - Always verify in production
  5. Handle retries - Make your handler idempotent
  6. Use filters - Reduce unnecessary webhook calls
  7. Set appropriate timeouts - Balance reliability and speed

Need help debugging webhooks? Check the webhook logs in your dashboard or contact support with your request ID.