Overview
Webhooks allow you to receive real-time notifications when specific events occur in the platform. When an event happens, the platform will send an HTTP POST request to your configured endpoint with details about the event.
Configuration
To receive webhooks, you need to configure:
- An endpoint URL where you want to receive the notifications
- Must use HTTPS (HTTP is not supported)
- SSL certificate must be valid (self-signed certificates are rejected)
- A webhook secret key for security verification (optional)
These are configured through the API section parameters:
Endpoint URL: Your HTTPS endpoint URLWebhook Secret: Your secret key for signature verification (optional)
Per-Resource Configuration
Webhook endpoints are configured separately for each resource type. This allows you to send different types of events to different endpoints. For example, you might send invoice webhooks to your billing system and customer webhooks to your CRM.
All platform objects support webhooks. The resource type in the webhook payload matches the object type used in the API endpoints. Common resource types include customer, invoice, transaction, number, and feature.
Your endpoint URL can include template variables that will be replaced with actual values when the webhook is sent:
{resourceType}- The type of resource that triggered the event{resourceID}- The ID of the specific resource{eventType}- The type of event that occurred
Example endpoints:
https://api.your-domain.com/webhooks
https://api.your-domain.com/webhooks/{resourceType}/{resourceID}
https://your-domain.com/api/incoming/{eventType}
Event Types
The eventType field in the webhook payload indicates what action triggered the event. Common event types include:
| Event Type | Description |
|---|---|
| Created | A new resource was added |
| Modified | An existing resource was updated |
| Dropped | A resource was deactivated (e.g., customer dropped, number released) |
| Reinstated | A previously dropped resource was reactivated |
| Deleted | A resource was permanently removed |
| Approved | A resource was approved (e.g., invoice approved) |
| Unapproved | Approval was removed from a resource |
The exact event type names depend on your platform configuration and the activity that triggered the webhook. Event type values are case-sensitive and typically use title case.
Security
If you have configured a webhook secret, each webhook request will include a signature header that you should verify before processing the payload:
- We include an
X-Webhook-Signatureheader containing a HMAC SHA-256 hash of the raw request body - The hash is created using your webhook secret as the key
- You should calculate the same hash on your side and verify it matches
If no webhook secret is configured, the signature header will not be present and this verification step can be skipped.
Note: The examples use timing-safe comparison functions (hash_equals in PHP, hmac.compare_digest in Python) which are best practice for cryptographic comparisons.
Example verification in PHP:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? null;
$secret = 'your-webhook-secret';
// Only verify if signature is present
if ($signature !== null) {
$calculated = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($calculated, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
}
Example in Python:
import hmac
import hashlib
payload = request.get_data() # Flask example
signature = request.headers.get('X-Webhook-Signature')
secret = b'your-webhook-secret'
# Only verify if signature is present
if signature:
calculated = hmac.new(secret, payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(calculated, signature):
return 'Invalid signature', 401
HTTP Headers
Each webhook request includes the following HTTP headers:
| Header | Description |
|---|---|
Content-Type | Always application/json |
User-Agent | SAFE-Webhook/1.0 |
X-Idempotency-Key | Unique identifier (UUID) for this webhook delivery |
X-Webhook-Signature | HMAC SHA-256 signature (only if webhook secret is configured) |
Use the X-Idempotency-Key to detect duplicate deliveries if the same webhook is retried.
Webhook Payload
Each webhook POST request includes a JSON payload with two main sections:
An
eventobject containing:resourceType: The type of resource that triggered the eventresourceID: The identifier of the resource (may be null for system-wide events)eventType: The type of event that occurredeventReason: The reason for the event (e.g., “Ad-hoc Invoice”)eventDetails: Description of the event (e.g., “Setup of new service”)
A resource object (named according to the resourceType) containing the state of the resource. Note that this object:
- Will be omitted for system-wide events that don’t relate to a specific resource
- Will be omitted for deletion events
- Will be omitted if the resource is no longer accessible
Example: Invoice Created
{
"event": {
"resourceType": "invoice",
"resourceID": "45678",
"eventType": "Created",
"eventReason": "Ad-hoc Invoice",
"eventDetails": "Setup of new service"
},
"invoice": {
"id": 45678,
"customerID": 1234,
"invoiceNumber": 10542,
"invoiceDate": "2025-01-15",
"cutoffDate": "2025-01-31",
"dueDate": "2025-02-14",
"invoiceAmount": 150.00,
"invoiceVAT": 30.00,
"invoiceTotal": 180.00,
"outstandingBalance": 180.00,
"statusID": 1
}
}
Example: Customer Modified
{
"event": {
"resourceType": "customer",
"resourceID": "1234",
"eventType": "Modified",
"eventReason": "Contact Update",
"eventDetails": "Updated billing email address"
},
"customer": {
"id": 1234,
"customerName": "Acme Ltd",
"customerReference": "ACME-001",
"statusID": 1,
"billingEmail": "accounts@acme.example.com"
}
}
Example: Resource Deleted
When a resource is deleted, the webhook includes only the event details:
{
"event": {
"resourceType": "transaction",
"resourceID": "98765",
"eventType": "Deleted",
"eventReason": "Goodwill Gesture",
"eventDetails": "Delay in service going live"
}
}
Webhook Delivery
Request Format
- Webhooks are sent as HTTP POST requests
- The request body is JSON-encoded
- Each webhook includes:
- An
X-Idempotency-Keyheader with a unique identifier - An
X-Webhook-Signatureheader for verification (if secret configured)
- An
- Your endpoint must use HTTPS
- Your endpoint should return a 2xx HTTP status code to indicate successful receipt
Timeouts
- Connection timeout: 10 seconds (to establish the connection)
- Request timeout: 30 seconds (total time for the entire request including response)
Ensure your endpoint responds within these limits to avoid failed deliveries.
Retry Behaviour
Failed deliveries are retried with increasing delays:
- 1st retry: 1 minute
- 2nd retry: 5 minutes
- 3rd retry: 15 minutes
- Subsequent retries: 1 hour
Retries continue indefinitely until delivery succeeds. Use the X-Idempotency-Key header to detect and handle duplicate deliveries.
Ordering Guarantee
Webhooks for the same account are delivered in chronological order. This ensures you receive events in the sequence they occurred. Events for different accounts may be delivered in parallel.
Best Practices
- Verify signatures: If using a webhook secret, always verify the signature before processing
- Respond quickly: Ensure your endpoint responds within the 30-second timeout
- Handle duplicates: Check the
X-Idempotency-Keyheader to prevent duplicate processing - Store before processing: Save the raw webhook data before processing it
- Process asynchronously: Queue webhooks for background processing if they require significant work
Technical Notes
Historical Data
If a resource has been modified since the event occurred, the webhook includes the resource state at the time of the event (from the audit trail), not the current state. This ensures you receive accurate point-in-time data.
If you need the current state of the resource, use the resourceType and resourceID from the event to fetch it via the API endpoints.
Resource Availability
The resource object may be omitted from the webhook payload when:
- The event doesn’t relate to a specific resource (system-wide events)
- The resource has been permanently deleted
- The resource is no longer accessible to your user account
In these cases, use the resourceType and resourceID from the event object to identify the affected resource.