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 OKstatus code within 5 seconds
Enabling Webhooks
You can manage your own Webhook Subscriptions directly through the API — see Managing Subscriptions:
- Discover the events available to you (
GET {baseUrl}/webhooks) - Subscribe with your endpoint URL and the events you want (
POST {baseUrl}/webhooks) — this returns your signing secret once - Verify & test delivery against your endpoint (see Signature Validation below)
- Update the subscription any time (
PATCH {baseUrl}/webhooks/{id}) - 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 OKstatus 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
Authorizationheader 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
- Idempotency: Use the
event_idto ensure you process each event only once - Quick Response: Acknowledge receipt quickly and process events asynchronously
- Error Handling: Implement proper error handling and logging
- Validation: Verify webhook authenticity using provided signatures and optional
Authorizationheader (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
- Signature Generation: We generate an HMAC-SHA256 signature using your webhook secret key and the raw request body
- Header Inclusion: The signature is included in the
X-Webhook-Signatureheader assha256=<signature> - 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
-
Webhooks Not Received
- Verify your endpoint is publicly accessible
- Check firewall and security group settings
- Ensure you're returning a 200 status code
-
Duplicate Events
- Implement idempotency using
event_id - Check for multiple webhook configurations
- Implement idempotency using
-
Webhook Disabled
- Check with support if webhooks were disabled due to delivery failures
- Review endpoint logs for errors
-
Unauthorized or invalid token
- Check if the
Authorizationheader 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
- Check if the
-
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:
- Setting up a test endpoint using tools like ngrok for local development
- Requesting test events from our support team
- 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.