Skip to main content

Webhooks Overview

When an event occurs (such as an order status change, filings error or document status change) or if more (or corrected) information is needed, we'll send a HTTP POST request to the URL you've configured. This allows your application to react immediately to important events without the need for constant API polling.

Getting Started

Prerequisites

Before setting up webhooks, ensure that:

  • Your account has webhook functionality enabled (contact our support team if needed)
  • You have a publicly accessible HTTPS endpoint ready to receive webhook events
  • Your endpoint can respond with a 200 OK status code within 5 seconds

Enabling Webhooks

You can manage your own Webhook Subscriptions directly through the API — see Managing Subscriptions:

  1. Discover the events available to you (GET {baseUrl}/webhooks)
  2. Subscribe with your endpoint URL and the events you want (POST {baseUrl}/webhooks) — this returns your signing secret once
  3. Verify & test delivery against your endpoint (see Signature Validation below)
  4. Update the subscription any time (PATCH {baseUrl}/webhooks/{id})
  5. Unsubscribe to permanently remove it (DELETE {baseUrl}/webhooks/{id})

If you would rather have us configure it for you, contact our support team with your endpoint URL.

Webhook Configuration

Endpoint Requirements

  • Protocol: Only HTTPS endpoints are supported for security
  • Response Time: Your endpoint must respond within 10 seconds
  • Response Code: Return a 200 OK status code to acknowledge receipt
  • Content Handling: Be prepared to handle JSON payloads

Request Format

All webhook requests are sent with:

  • Method: POST
  • Content-Type: application/json
  • Headers: Standard HTTP headers plus an optional Authorization header that you can use to authenticate the request.

Payload Structure

Each webhook payload includes:

{
"event_id": "1234567890",
"event": {
"name": "order_status_change",
"changed_field": "status",
"old_status": "Active",
"new_status": "Cancelled"
},
"data": {
// Event-specific data
"order": {
...
}
}
}

Retries

If your webhook endpoint is unreachable, we'll retry delivering the webhook 3 times (by default) after first request in the following times: 1, 2 and 4 minutes. If we don't receive a 200 OK status code, we'll stop retrying and mark the webhook as failed. If the webhook is successfully delivered, we'll mark it as delivered.

Event Types

Common webhook events include:

  • Order Status Updates: When order status changes

Check the Webhooks List for a complete list of available events and their payload structures.

Best Practices

Handling Webhooks

  1. Idempotency: Use the event_id to ensure you process each event only once
  2. Quick Response: Acknowledge receipt quickly and process events asynchronously
  3. Error Handling: Implement proper error handling and logging
  4. Validation: Verify webhook authenticity using provided signatures and optional Authorization header (bearer token)

Endpoint Implementation

// Example webhook endpoint
app.post('/webhooks', (req, res) => {
const { event_type, event_id, data } = req.body;

// Acknowledge receipt immediately
res.status(200).send('OK');

// if token is setup in the header, verify it. Token should be parsed as Bearer token.
if (req.headers['authorization']) {
const token = req.headers['authorization'].split(' ')[1];
if (token !== process.env.WEBHOOK_TOKEN) {
console.log('Invalid webhook token');
return res.status(401).send('Unauthorized');
}
}

// Verify webhook signature
const payload = req.body;
const secret = process.env.WEBHOOK_SECRET;
const signature = req.headers['x-webhook-signature'];

if (!verifyWebhookSignature(payload, signature, secret)) {
console.log('Invalid webhook signature');
return;
}

// Process event asynchronously
processWebhookEvent(event_type, event_id, data);
});

Security Considerations

Network Security

  • HTTPS Only: All webhook endpoints must use HTTPS encryption
  • IP Allowlisting: Consider restricting access to our webhook IP ranges
  • Firewall Configuration: Ensure your firewall allows incoming connections from our servers

Data Protection

  • Signature Verification: Implement webhook signature verification to ensure authenticity
  • Rate Limiting: Implement appropriate rate limiting on your webhook endpoint
  • Data Validation: Always validate incoming webhook data before processing

Signature Validation

All webhook requests include a signature header that you should validate to ensure the request is authentic and from our servers.

How It Works

  1. Signature Generation: We generate an HMAC-SHA256 signature using your webhook secret key and the raw request body
  2. Header Inclusion: The signature is included in the X-Webhook-Signature header as sha256=<signature>
  3. Verification: Your endpoint should generate the same signature and compare it with the received one

Implementation

Getting Your Secret Key Your webhook secret key is returned once, in the response to POST {baseUrl}/webhooks when you create the subscription. Store it securely at that point — it cannot be retrieved later. If webhooks were configured for you by support, contact support to obtain your secret key.

Example secret key format: whsec_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890

Implementation Examples

PHP Implementation

<?php

/**
* Verify webhook signature
*
* @param string $payload The raw request body (JSON string)
* @param string $receivedSignature The signature from X-Webhook-Signature header
* @param string $secretKey Your webhook secret key
* @return bool True if signature is valid, false otherwise
*/
function verifyWebhookSignature(string $payload, string $receivedSignature, string $secretKey): bool
{
// Remove 'sha256=' prefix if present
$receivedSignature = str_replace('sha256=', '', $receivedSignature);

// Generate expected signature
$expectedSignature = hash_hmac('sha256', $payload, $secretKey);

// Use timing-safe comparison to prevent timing attacks
return hash_equals($expectedSignature, $receivedSignature);
}

// Usage in your webhook endpoint
$payload = file_get_contents('php://input'); // Get raw request body
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secretKey = 'whsec_your_secret_key_here';

if (!verifyWebhookSignature($payload, $signature, $secretKey)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}

// Process the webhook
$data = json_decode($payload, true);
// ... handle webhook data

Laravel Implementation

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class WebhookController extends Controller
{
public function handle(Request $request): JsonResponse
{
$signature = $request->header('X-Webhook-Signature');
$payload = $request->getContent(); // Raw request body
$secretKey = config('webhooks.secret_key');

if (!$this->verifySignature($payload, $signature, $secretKey)) {
return response()->json(['error' => 'Invalid signature'], 401);
}

// Process webhook
$data = $request->all();

// ... handle webhook data

return response()->json(['status' => 'success']);
}

private function verifySignature(string $payload, ?string $receivedSignature, string $secretKey): bool
{
if (!$receivedSignature) {
return false;
}

$receivedSignature = str_replace('sha256=', '', $receivedSignature);
$expectedSignature = hash_hmac('sha256', $payload, $secretKey);

return hash_equals($expectedSignature, $receivedSignature);
}
}

Node.js Implementation

const crypto = require('crypto');

/**
* Verify webhook signature
*
* @param {string} payload - The raw request body (JSON string)
* @param {string} receivedSignature - The signature from X-Webhook-Signature header
* @param {string} secretKey - Your webhook secret key
* @returns {boolean} True if signature is valid, false otherwise
*/
function verifyWebhookSignature(payload, receivedSignature, secretKey) {
// Remove 'sha256=' prefix if present
receivedSignature = receivedSignature.replace('sha256=', '');

// Generate expected signature
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(payload)
.digest('hex');

// Use timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);
}

// Usage in Express.js
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = req.body.toString();
const secretKey = process.env.WEBHOOK_SECRET_KEY;

if (!verifyWebhookSignature(payload, signature, secretKey)) {
return res.status(401).json({ error: 'Invalid signature' });
}

// Process webhook
const data = JSON.parse(payload);
// ... handle webhook data

res.json({ status: 'success' });
});

Python Implementation

import hmac
import hashlib

def verify_webhook_signature(payload: str, received_signature: str, secret_key: str) -> bool:
"""
Verify webhook signature

Args:
payload: The raw request body (JSON string)
received_signature: The signature from X-Webhook-Signature header
secret_key: Your webhook secret key

Returns:
True if signature is valid, False otherwise
"""
# Remove 'sha256=' prefix if present
received_signature = received_signature.replace('sha256=', '')

# Generate expected signature
expected_signature = hmac.new(
secret_key.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()

# Use timing-safe comparison
return hmac.compare_digest(expected_signature, received_signature)

# Usage in Flask
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_data(as_text=True)
secret_key = 'whsec_your_secret_key_here'

if not verify_webhook_signature(payload, signature, secret_key):
return jsonify({'error': 'Invalid signature'}), 401

# Process webhook
data = request.json
# ... handle webhook data

return jsonify({'status': 'success'})

Ruby Implementation

require 'openssl'

# Verify webhook signature
#
# @param payload [String] The raw request body (JSON string)
# @param received_signature [String] The signature from X-Webhook-Signature header
# @param secret_key [String] Your webhook secret key
# @return [Boolean] True if signature is valid, false otherwise
def verify_webhook_signature(payload, received_signature, secret_key)
# Remove 'sha256=' prefix if present
received_signature = received_signature.gsub('sha256=', '')

# Generate expected signature
expected_signature = OpenSSL::HMAC.hexdigest('SHA256', secret_key, payload)

# Use timing-safe comparison
Rack::Utils.secure_compare(expected_signature, received_signature)
end

# Usage in Rails controller
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token

def create
signature = request.headers['X-Webhook-Signature']
payload = request.raw_post
secret_key = ENV['WEBHOOK_SECRET_KEY']

unless verify_webhook_signature(payload, signature, secret_key)
render json: { error: 'Invalid signature' }, status: :unauthorized
return
end

# Process webhook
data = JSON.parse(payload)
# ... handle webhook data

render json: { status: 'success' }
end

private

def verify_webhook_signature(payload, received_signature, secret_key)
received_signature = received_signature.gsub('sha256=', '')
expected_signature = OpenSSL::HMAC.hexdigest('SHA256', secret_key, payload)
Rack::Utils.secure_compare(expected_signature, received_signature)
end
end

Important Notes

  • Always use the raw request body (before JSON parsing) for signature verification
  • Use constant-time comparison functions to prevent timing attacks
  • Store your webhook secret securely (environment variables, secrets manager, etc.)
  • Validate the signature before processing any webhook data

Monitoring

  • Response Monitoring: We monitor webhook delivery success rates
  • Automatic Disabling: Webhooks may be automatically disabled if:
    • Your endpoint consistently returns non-200 status codes
    • Your endpoint times out frequently
    • Your endpoint is unreachable for an extended period

Troubleshooting

Common Issues

  1. Webhooks Not Received

    • Verify your endpoint is publicly accessible
    • Check firewall and security group settings
    • Ensure you're returning a 200 status code
  2. Duplicate Events

    • Implement idempotency using event_id
    • Check for multiple webhook configurations
  3. Webhook Disabled

    • Check with support if webhooks were disabled due to delivery failures
    • Review endpoint logs for errors
  4. Unauthorized or invalid token

    • Check if the Authorization header is set and the token is correct
    • Check if the token is set in the environment variables
    • Check if the token is set in the header
    • Check if the token is set in the request body
    • Check if the token is valid
  5. Invalid Signature

    • Check if the signature is set in the header
    • Check if the signature is set in the request body
    • Check if the signature is correct
    • Check if the signature is valid

Testing Your Integration

You can test your webhook endpoint by:

  1. Setting up a test endpoint using tools like ngrok for local development
  2. Requesting test events from our support team
  3. Monitoring webhook delivery in your application logs

Support

If you need assistance with webhook setup or troubleshooting:

  • Contact our support team for account-level webhook configuration
  • Refer to our API documentation for technical implementation details
  • Monitor your webhook endpoint logs for delivery and processing issues

For immediate assistance, please contact our support team with your account details and webhook configuration requirements.