Skip to content

Webhook Administration

Arbitex can push real-time event notifications to external HTTP endpoints via webhooks. When a subscribed event fires, Arbitex delivers a signed HTTP POST to the configured URL. All webhook management endpoints require an admin user.

ConceptDescription
WebhookA named subscription pairing a URL with one or more event types
Event typeA string identifying the kind of event (e.g., "new_conversation")
SecretA shared string used to compute the HMAC-SHA256 signature on each delivery
DeliveryA single HTTP POST attempt to the webhook URL for a specific event
Event typeWhen it fires
new_conversationA user creates a new conversation
dlp_triggerA DLP rule matches content in a message
quota_exceededA user or group quota is exceeded
bundle_state_changeA compliance bundle changes state (enabled/disabled/acknowledged)

A webhook can subscribe to multiple event types in the same registration.

All endpoints are prefixed /api/admin/webhooks and require an admin JWT.

GET /api/admin/webhooks/
Authorization: Bearer <admin-token>

Returns all webhooks for the tenant, ordered by created_at descending.

Response 200 OK:

[
{
"id": "3f8a1b2c-...",
"name": "SIEM Integration",
"url": "https://siem.example.com/arbitex/events",
"events": ["dlp_trigger", "quota_exceeded"],
"enabled": true,
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:00:00Z"
}
]
GET /api/admin/webhooks/{webhook_id}
Authorization: Bearer <admin-token>

Returns the webhook plus its 20 most recent delivery attempts.

Response 200 OK:

{
"id": "3f8a1b2c-...",
"name": "SIEM Integration",
"url": "https://siem.example.com/arbitex/events",
"events": ["dlp_trigger"],
"enabled": true,
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:00:00Z",
"recent_deliveries": [
{
"id": "d1a2b3c4-...",
"webhook_id": "3f8a1b2c-...",
"event_type": "dlp_trigger",
"payload": { ... },
"status": "delivered",
"attempts": 1,
"last_error": null,
"response_status": 200,
"created_at": "2026-03-12T14:22:00Z"
}
]
}
POST /api/admin/webhooks/
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"name": "SIEM Integration",
"url": "https://siem.example.com/arbitex/events",
"events": ["dlp_trigger", "quota_exceeded"],
"secret": "my-shared-secret-32chars+",
"enabled": true
}

Field constraints:

FieldConstraint
name1–255 characters
urlValid HTTPS URL, max 2000 characters
eventsNon-empty list of valid WebhookEventType values
secret8–255 characters
enabledDefault true

Response 201 Created — the created WebhookResponse object.

PUT /api/admin/webhooks/{webhook_id}
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"enabled": false
}

All fields are optional. Only provided fields are updated; omitted fields retain their current values.

Response 200 OK — the updated WebhookResponse object.

DELETE /api/admin/webhooks/{webhook_id}
Authorization: Bearer <admin-token>

Response 204 No Content

Deleting a webhook also deletes all associated delivery records (cascade).

Send a test webhook_test event to verify connectivity and HMAC signing:

POST /api/admin/webhooks/{webhook_id}/test
Authorization: Bearer <admin-token>

The test payload sent to the webhook URL:

{
"event_type": "webhook_test",
"payload": {
"message": "This is a test webhook delivery from Arbitex.",
"webhook_id": "3f8a1b2c-...",
"webhook_name": "SIEM Integration"
},
"timestamp": "2026-03-12T14:22:00Z"
}

Response 200 OK:

{
"success": true,
"status_code": 200,
"error": null
}

On failure (e.g., timeout, non-2xx response):

{
"success": false,
"status_code": 503,
"error": "HTTP 503: Service Unavailable"
}

Each delivery is an HTTP POST with these headers:

Content-Type: application/json
X-Webhook-Signature: <hex-encoded HMAC-SHA256>

The body has three top-level fields:

{
"event_type": "dlp_trigger",
"payload": { ... },
"timestamp": "2026-03-12T14:22:00Z"
}

The X-Webhook-Signature header is the HMAC-SHA256 of the raw JSON body bytes, computed with the webhook’s shared secret as the key.

To verify on your endpoint:

import hashlib, hmac
def verify_signature(body_bytes: bytes, secret: str, header_sig: str) -> bool:
expected = hmac.new(secret.encode(), body_bytes, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header_sig)

Always use hmac.compare_digest (constant-time comparison) to prevent timing attacks.

Arbitex retries failed deliveries with exponential backoff:

AttemptDelay before attempt
1Immediate
21 second
32 seconds

After 3 attempts, the delivery record is marked "failed". No further automatic retries are performed.

Triggers for retry:

  • Non-2xx HTTP response from the target URL
  • Connection timeout (10 second limit per attempt)
  • Network error

Triggers for immediate failure (no retry):

  • URL blocked by SSRF protection (see below)

Each HTTP request has a 10-second timeout. If the target endpoint does not respond within 10 seconds, the attempt is marked as a timeout error and retried.

Delivery status is visible in the webhook detail endpoint (GET /api/admin/webhooks/{id}) via the recent_deliveries array (last 20 deliveries).

StatusMeaning
"pending"Delivery attempt in progress
"delivered"HTTP 2xx received from target
"failed"All 3 attempts exhausted without success

There is no automatic dead-letter queue or re-delivery mechanism. Failed deliveries remain in the webhook_deliveries table with status "failed" for audit purposes. To re-send a failed event, use the test endpoint or wait for the next occurrence of the event.

For high-reliability integrations, configure your endpoint to acknowledge all deliveries with a 200 OK immediately (even before processing), then process asynchronously from a local queue.

The delivery service validates each webhook URL with an SSRF guard before making any HTTP request. URLs that resolve to private, loopback, link-local, or reserved IP ranges are blocked:

  • 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
  • 127.0.0.0/8 (localhost)
  • 169.254.0.0/16 (link-local)
  • Cloud metadata endpoints (e.g., 169.254.169.254)

Blocked deliveries are recorded with status "failed" and last_error: "URL blocked by SSRF protection: target resolves to a private, loopback, or reserved IP address". No retry is performed.

The Cloud Portal has a separate webhook surface for outpost health and dead-letter notifications, exposed at:

GET /v1/orgs/{org_id}/notifications

This endpoint returns:

  • Outpost heartbeat health alerts (outposts not seen for more than 5 minutes)
  • Webhook dead-letter notifications (deliveries that failed after all retries in cloud-connected orgs)

Portal-level notifications are distinct from the platform webhook subscriptions documented above. Platform webhooks fire on conversation/DLP/quota/compliance events; portal notifications surface infrastructure health for operators.

  1. Check GET /api/admin/webhooks/{id} — review recent_deliveries for last_error values.
  2. Confirm the webhook is "enabled": true.
  3. Test connectivity with POST /api/admin/webhooks/{id}/test.
  4. Verify the target URL is reachable from the Arbitex server and is not a private IP.
  • Ensure you compute HMAC-SHA256 over the raw request body bytes (not a re-serialised JSON object).
  • Confirm the secret used in your verification matches the secret set during webhook creation. Secrets are not returned by GET endpoints — if you have lost the secret, update the webhook with a new one via PUT.
  • Verify the event_type string in the received payload matches what your handler expects.
  • Ensure the webhook’s events array includes the event type you want to receive.
  • Ensure your endpoint responds within 10 seconds. For slow consumers, respond 200 OK immediately and process the payload asynchronously.