Webhooks

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:

  1. 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)
  2. A webhook secret key for security verification (optional)

These are configured through the API section parameters:

  • Endpoint URL: Your HTTPS endpoint URL
  • Webhook 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 TypeDescription
CreatedA new resource was added
ModifiedAn existing resource was updated
DroppedA resource was deactivated (e.g., customer dropped, number released)
ReinstatedA previously dropped resource was reactivated
DeletedA resource was permanently removed
ApprovedA resource was approved (e.g., invoice approved)
UnapprovedApproval 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:

  1. We include an X-Webhook-Signature header containing a HMAC SHA-256 hash of the raw request body
  2. The hash is created using your webhook secret as the key
  3. 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:

HeaderDescription
Content-TypeAlways application/json
User-AgentSAFE-Webhook/1.0
X-Idempotency-KeyUnique identifier (UUID) for this webhook delivery
X-Webhook-SignatureHMAC 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:

  1. An event object containing:

    • resourceType: The type of resource that triggered the event
    • resourceID: The identifier of the resource (may be null for system-wide events)
    • eventType: The type of event that occurred
    • eventReason: The reason for the event (e.g., “Ad-hoc Invoice”)
    • eventDetails: Description of the event (e.g., “Setup of new service”)
  2. 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-Key header with a unique identifier
    • An X-Webhook-Signature header for verification (if secret configured)
  • 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

  1. Verify signatures: If using a webhook secret, always verify the signature before processing
  2. Respond quickly: Ensure your endpoint responds within the 30-second timeout
  3. Handle duplicates: Check the X-Idempotency-Key header to prevent duplicate processing
  4. Store before processing: Save the raw webhook data before processing it
  5. 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.

Still Didn’t Find Your Answer?

For assistance, please contact us below.

Submit a ticket