Skip to main content

Overview

Webhooks provide real-time notifications when document processing status changes occur. When configured, your application will receive HTTP POST requests to specified endpoints whenever documents transition through different processing states. Webhooks are essential for building responsive applications that need immediate awareness of document processing results, failures, or other status changes.

Webhook Delivery

Delivery Mechanism

Webhooks are delivered via HTTP POST requests to your configured endpoint:
  • Method: POST
  • Content-Type: application/json
  • Authentication: Include X-Webhook-Token header with your webhook token
  • Payload: JSON:API formatted document status change event

Retry Policy

Our webhook system implements intelligent retry logic:
  • Initial Delivery: Immediate attempt after status change
  • Max Retries: 5 attempts before marking as failed
  • Dead Letter Queue: Failed deliveries are queued for manual retry

Security

1

Secure your endpoint

Always use HTTPS endpoints in production. HTTP is only allowed in sandbox environments.
2

Validate webhook tokens

Verify the X-Webhook-Token header matches your stored webhook token. Store tokens securely - they cannot be retrieved after creation.
3

Implement idempotency

Use the document ID and status from the webhook payload to handle duplicate deliveries gracefully.

Verifying Webhook Signatures

You can use the X-Webhook-Signature header to verify that the webhook payload was sent by Beltic. The signature is generated using an HMAC-SHA256 of the request body signed with your webhook secret. Here is a complete example of how to verify the signature in a Node.js application:
import crypto from 'crypto';

/**
 * Example function to verify a webhook signature
 *
 * @param payload - The raw request body as a string
 * @param signature - The X-Webhook-Signature header value
 * @param secret - The secret key provided when creating the webhook
 * @returns boolean - True if signature is valid
 */
export function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  if (!payload || !signature || !secret) {
    return false;
  }

  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  // Use timingSafeEqual to prevent timing attacks
  const signatureBuffer = Buffer.from(signature);
  const expectedBuffer = Buffer.from(expectedSignature);

  if (signatureBuffer.length !== expectedBuffer.length) {
    return false;
  }

  return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);
}

// Example usage with Express
import express from 'express';

const app = express();

// Use raw body parser to ensure exact payload matching
app.use(express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const secret = process.env.WEBHOOK_SECRET;

  if (!verifyWebhookSignature(req.body.toString(), signature, secret)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString());
  // Process event...
});

Creating a Webhook Configuration

1

Prepare your endpoint

Create an HTTPS endpoint capable of receiving POST requests with JSON payloads. The endpoint should:
  • Accept application/json content type
  • Validate the X-Webhook-Token header
  • Return 2xx status codes for successful processing
  • Handle duplicate deliveries gracefully
// Example webhook receiver (Node.js/Express)
app.post('/webhooks/documents', (req, res) => {
  const token = req.headers['x-webhook-token'];

  // Validate token
  if (token !== process.env.WEBHOOK_TOKEN) {
    return res.status(401).json({ error: 'Invalid token' });
  }

  // Process webhook payload
  const { data } = req.body;
  console.log(`Document ${data.id} status changed to ${data.attributes.status}`);

  // Return success
  res.status(200).json({ received: true });
});
2

Choose subscribed statuses

Select which document status changes should trigger webhooks:
  • processed: Document processing completed successfully
  • failed: Document processing failed
3

Create the webhook configuration

Use the Create Webhook Config endpoint:
curl -X POST https://api.beltic.com/v1/webhooks/configs \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "type": "webhook_config",
      "attributes": {
        "url": "https://your-app.com/webhooks/documents",
        "subscribed_statuses": ["processed", "failed"]
      }
    }
  }'
Important: Store the returned token securely - it cannot be retrieved again.

Listing Webhook Configurations

Retrieve all webhook configurations for your organization:
curl -X GET "https://api.beltic.com/v1/webhooks/configs" \
  -H "X-Api-Key: YOUR_API_KEY"

Filtering Options

  • Active configs only: Include ?filter[is_active]=true
  • Pagination: Use page[size] (default: 15, max: 100)

Managing Webhook Configurations

Retrieving a Specific Configuration

Get detailed information about a webhook config:
curl -X GET https://api.beltic.com/v1/webhooks/configs/{config_id} \
  -H "X-Api-Key: YOUR_API_KEY"

Updating a Configuration

Modify webhook settings using the update endpoint:
curl -X PATCH https://api.beltic.com/v1/webhooks/configs/{config_id} \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "type": "webhook_config",
      "id": "{config_id}",
      "attributes": {
        "url": "https://your-app.com/webhooks/documents/v2",
        "subscribed_statuses": ["processed", "failed"],
        "is_active": true
      }
    }
  }'

Deactivating a Configuration

Temporarily disable a webhook without deleting it:
curl -X PATCH https://api.beltic.com/v1/webhooks/configs/{config_id} \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "type": "webhook_config",
      "id": "{config_id}",
      "attributes": {
        "is_active": false
      }
    }
  }'

Webhook Payload Format

All webhook deliveries use JSON:API format:
{
  "data": {
    "type": "document",
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "attributes": {
      "status": "processed",
      "created_at": "2026-01-16T10:00:00.000Z",
      "updated_at": "2026-01-16T10:30:00.000Z"
    },
    "relationships": {
      "document_template": {
        "data": {
          "type": "document_template",
          "id": "456e7890-e89b-12d3-a456-426614174000"
        }
      }
    }
  },
  "meta": {
    "event_type": "document.status_changed",
    "previous_status": "processing",
    "timestamp": "2026-01-16T10:30:00.000Z"
  }
}

Handling Webhook Failures

Manual Resend

If a webhook was missed or needs to be re-delivered, use the resend endpoint:
curl -X POST https://api.beltic.com/v1/webhooks/resend \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "type": "webhook_resend",
      "attributes": {
        "document_id": "123e4567-e89b-12d3-a456-426614174000"
      }
    }
  }'
This creates a new delivery attempt for the specified document/status combination.

Testing Webhooks

Development Testing

Use tools like ngrok or localtunnel to expose local endpoints:
# Using ngrok to expose local port 3000
ngrok http 3000

# Configure webhook with ngrok URL
curl -X POST https://api.beltic.com/v1/webhooks/configs \
  -H "X-Api-Key: YOUR_SANDBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "type": "webhook_config",
      "attributes": {
        "url": "https://abc123.ngrok.io/webhooks/documents",
        "subscribed_statuses": ["processed", "failed"]
      }
    }
  }'

Manual Testing

Test webhook delivery by manually resending:
# Resend webhook for a specific document
curl -X POST https://api.beltic.com/v1/webhooks/resend \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "type": "webhook_resend",
      "attributes": {
        "document_id": "your-document-id",
        "webhook_config_id": "your-webhook-config-id"
      }
    }
  }'

Best Practices

  • Use HTTPS endpoints with valid SSL certificates
  • Implement proper logging for webhook deliveries
  • Return appropriate HTTP status codes (200 for success, 4xx/5xx for errors)
  • Process webhooks asynchronously to avoid timeouts
  • Always validate the X-Webhook-Token header
  • Store webhook tokens securely (environment variables, secret management)
  • Implement rate limiting to protect against abuse
  • Use idempotency keys to handle duplicate deliveries
  • Log all webhook deliveries with timestamps and payloads
  • Implement alerting for high failure rates
  • Have fallback mechanisms for critical webhook-dependent features
  • Monitor webhook endpoint health and performance
  • Use different webhook URLs for different environments
  • Document webhook configurations and their purposes
  • Regularly review and clean up unused webhook configurations
  • Test webhook configurations after deployments

Troubleshooting

Common Issues

Webhook Not Received
  • Check that the webhook configuration is active
  • Verify the endpoint URL is accessible and returns 2xx responses
  • Ensure the document status is in your subscribed statuses list
Authentication Failures
  • Verify the X-Webhook-Token header is included
  • Confirm the token matches what was returned during configuration creation
  • Check for token encoding issues
Duplicate Deliveries
  • Implement idempotency using document ID and status
  • Log all received webhooks to detect duplicates
  • Handle duplicate deliveries gracefully
Timeout Issues
  • Process webhooks asynchronously in background jobs
  • Return immediate 202 Accepted responses
  • Implement proper error handling and retries in your application