Home

Webhooks

Webhooks let Karbon push notifications to your server when data changes, eliminating the need to poll for updates.

Overview

When a subscribed event occurs, Karbon sends a POST request to your endpoint with a lightweight payload identifying what changed. You then fetch the full record if needed.

Delivery behaviour:

  • One notification is sent per entity change (create, update, delete)
  • Notifications are coalesced — there is a 60-second window before a notification is sent, so rapid changes to the same entity result in a single notification rather than many
  • Bulk operations in Karbon (e.g. updating many clients at once) can generate a large number of webhook notifications in a short period — ensure your endpoint can handle bursts and processes payloads asynchronously

Payload format:

{
  "ResourcePermaKey": "{EntityKey}",
  "ResourceType": "Contact",
  "ActionType": "Updated",
  "TimeStamp": "2026-03-31T14:22:00Z"
}

Available Webhook Types

TypeTriggers on
ContactContacts, Organizations, and ClientGroups
WorkWork items
NoteNotes
UserUsers
InvoiceInvoices
EstimateSummaryEstimate summaries
CustomFieldCustom field definitions
IntegrationTaskIntegration tasks (partners only)

Creating a Subscription

POST https://api.karbonhq.com/v3/WebhookSubscriptions
Authorization: Bearer {token}
AccessKey: {key}
Content-Type: application/json

{
  "WebhookType": "Work",
  "TargetUrl": "https://your-app.example.com/webhooks/karbon",
  "SigningKey": "your-secret-signing-key-min-16-chars"
}

SigningKey is optional but recommended — it’s used to sign webhook payloads so you can verify they genuinely came from Karbon. It must be at least 16 characters and contain only letters, numbers, dashes, or underscores.

Important: You can only have one active subscription per webhook type per API application.

Checking a Subscription

GET https://api.karbonhq.com/v3/WebhookSubscriptions/Work
Authorization: Bearer {token}
AccessKey: {key}

A 404 response means either no subscription exists or it was auto-cancelled due to delivery failures.

Deleting a Subscription

DELETE https://api.karbonhq.com/v3/WebhookSubscriptions/Work
Authorization: Bearer {token}
AccessKey: {key}

Auto-Cancellation

If your endpoint fails to respond with an HTTP 2xx status 10 consecutive times, Karbon automatically cancels the subscription. To resume receiving events:

  1. Fix whatever caused your endpoint to fail
  2. Re-create the subscription with a new POST

Build monitoring into your webhook endpoint to detect and alert on delivery failures before you hit the 10-failure threshold. You can call GET /v3/WebhookSubscriptions/{Type} on a schedule to verify if a webhook subscription is still active — a 404 response means the subscription was cancelled.

Handling Incoming Webhooks

Your endpoint must:

  1. Respond with HTTP 2xx quickly (before any timeout)
  2. Process the notification asynchronously if needed

A minimal example:

from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

KARBON_HEADERS = {
    "Authorization": "Bearer {token}",
    "AccessKey": "{key}"
}

@app.route("/webhooks/karbon", methods=["POST"])
def handle_webhook():
    payload = request.get_json()

    resource_key = payload["ResourcePermaKey"]
    resource_type = payload["ResourceType"]
    action_type = payload["ActionType"]

    # Acknowledge immediately
    # Process asynchronously (queue, background task, etc.)
    enqueue_task(resource_type, resource_key, action_type)

    return jsonify({"ok": True}), 200

def enqueue_task(resource_type, resource_key, action_type):
    # Your async processing logic here
    pass

Fetching the Changed Resource

The webhook payload only tells you what changed — it doesn’t include the full record. Fetch it using the ResourcePermaKey:

def fetch_changed_resource(resource_type, resource_key):
    endpoint_map = {
        "Contact": f"/v3/Contacts/{resource_key}",
        "Work": f"/v3/WorkItems/{resource_key}",
        "Invoice": f"/v3/Invoices/{resource_key}",
    }
    url = f"https://api.karbonhq.com{endpoint_map[resource_type]}"
    return requests.get(url, headers=KARBON_HEADERS).json()

Webhook Security

Validate that incoming requests are genuinely from Karbon before processing them.

Signature Verification

When you set a SigningKey, Karbon includes a Signature header on every delivery. The value is a lowercase hex-encoded HMAC-SHA256 digest of the raw JSON request body, keyed with your SigningKey.

Verifying this signature before processing a payload ensures the request genuinely came from Karbon and that the payload has not been tampered with in transit. Without verification, your endpoint will process any POST request sent to it, making it a potential vector for spoofed or malicious payloads.

Note: The SigningKey is not returned when you GET a subscription — store it securely at the time you create the subscription.

Additional measures

  • Restrict your webhook endpoint to Karbon’s IP ranges (check Developer Center documentation for current ranges)
  • Check the Content-Type header is application/json