Webhooks
Overview
Section titled “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
Section titled “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
Section titled “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/webhookshttps://api.your-domain.com/webhooks/{resourceType}/{resourceID}https://your-domain.com/api/incoming/{eventType}Event Types
Section titled “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
Section titled “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 presentif ($signature !== null) { $calculated = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($calculated, $signature)) { http_response_code(401); exit('Invalid signature'); }}Example in Python:
import hmacimport hashlib
payload = request.get_data() # Flask examplesignature = request.headers.get('X-Webhook-Signature')secret = b'your-webhook-secret'
# Only verify if signature is presentif signature: calculated = hmac.new(secret, payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(calculated, signature): return 'Invalid signature', 401HTTP Headers
Section titled “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
Section titled “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
Section titled “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", "transactionCutoffDate": "2025-01-20", "dueDate": "2025-02-14", "invoiceAmount": 150.00, "invoiceVAT": 30.00, "invoiceTotal": 180.00, "outstandingBalance": 180.00, "statusID": 1 }}Example: Customer Modified
Section titled “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
Section titled “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
Section titled “Webhook Delivery”Request Format
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “Technical Notes”Historical Data
Section titled “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
Section titled “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.