Skip to content

Webhooks API

The Webhooks API manages outbound HTTP integrations that deliver signed event notifications to external systems. All endpoints require admin authentication (Authorization: Bearer $ARBITEX_API_KEY).

Base URL: https://gateway.arbitex.ai


MethodPathDescription
GET/api/admin/webhooks/List all webhooks for the org
POST/api/admin/webhooks/Create a webhook
GET/api/admin/webhooks/{webhook_id}Get webhook with last 20 delivery records
PUT/api/admin/webhooks/{webhook_id}Update webhook (partial)
DELETE/api/admin/webhooks/{webhook_id}Delete webhook
POST/api/admin/webhooks/{webhook_id}/testSend a test delivery

FieldTypeDescription
idUUIDWebhook UUID
namestringHuman-readable label
urlstringHTTPS destination URL
eventslist[WebhookEventType]Event types this webhook subscribes to
secretstringHMAC signing secret — write-only after create; omitted from all GET responses
enabledboolWhether the webhook is active
created_atdatetimeISO 8601 creation timestamp
updated_atdatetimeISO 8601 last-modified timestamp
ValueWhen it fires
new_conversationA new conversation is created on the gateway
dlp_triggerA DLP rule matches content in a conversation
quota_exceededA user or group reaches or exceeds their usage quota
bundle_state_changeA compliance bundle transitions to a new state

GET /api/admin/webhooks/

Returns all webhook registrations for the tenant.

Request

Terminal window
curl -s https://gateway.arbitex.ai/api/admin/webhooks/ \
-H "Authorization: Bearer $ARBITEX_API_KEY"

Response 200 OK

[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "SIEM DLP pipeline",
"url": "https://ingest.example.com/arbitex-events",
"events": ["dlp_trigger", "quota_exceeded"],
"enabled": true,
"created_at": "2026-02-01T09:00:00Z",
"updated_at": "2026-03-10T14:22:00Z"
},
{
"id": "9c0e1d2f-3b4a-5c6d-7e8f-9a0b1c2d3e4f",
"name": "Compliance change notifier",
"url": "https://compliance.internal.example.com/hooks",
"events": ["bundle_state_change", "new_conversation"],
"enabled": false,
"created_at": "2026-01-15T11:30:00Z",
"updated_at": "2026-01-15T11:30:00Z"
}
]

GET /api/admin/webhooks/{webhook_id}

Returns the webhook registration plus the last 20 delivery records in reverse-chronological order.

Path parameters

ParameterTypeDescription
webhook_idUUIDThe webhook UUID

Request

Terminal window
curl -s https://gateway.arbitex.ai/api/admin/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6 \
-H "Authorization: Bearer $ARBITEX_API_KEY"

Response 200 OKWebhookDetailResponse

{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "SIEM DLP pipeline",
"url": "https://ingest.example.com/arbitex-events",
"events": ["dlp_trigger", "quota_exceeded"],
"enabled": true,
"created_at": "2026-02-01T09:00:00Z",
"updated_at": "2026-03-10T14:22:00Z",
"recent_deliveries": [
{
"id": "del-a1b2c3d4-...",
"webhook_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"event_type": "dlp_trigger",
"payload": {
"event_type": "dlp_trigger",
"payload": {
"request_id": "req_01abc123",
"user_id": "usr_01def456",
"rule_id": "credit-card-block",
"entity_type": "credit_card",
"action_taken": "BLOCK",
"timestamp": "2026-03-12T10:15:00Z"
},
"timestamp": "2026-03-12T10:15:00.482Z"
},
"response_status": 200,
"response_body": "OK",
"success": true,
"attempt_count": 1,
"delivered_at": "2026-03-12T10:15:01.103Z",
"created_at": "2026-03-12T10:15:00.482Z"
},
{
"id": "del-b2c3d4e5-...",
"webhook_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"event_type": "quota_exceeded",
"payload": { "event_type": "quota_exceeded", "payload": { "..." }, "timestamp": "2026-03-11T23:59:58Z" },
"response_status": null,
"response_body": null,
"success": false,
"attempt_count": 3,
"delivered_at": null,
"created_at": "2026-03-11T23:59:58Z"
}
]
}

POST /api/admin/webhooks/

Creates a new webhook registration. The secret field is returned only in the 201 response — it is never returned by subsequent GET requests. Store it securely.

Request body

FieldTypeRequiredDescription
namestringyesHuman-readable label
urlstringyesHTTPS destination URL
eventslist[string]yesOne or more WebhookEventType values
secretstringyesHMAC signing secret, minimum 8 characters
enabledboolnoDefault true

Request

Terminal window
curl -s -X POST https://gateway.arbitex.ai/api/admin/webhooks/ \
-H "Authorization: Bearer $ARBITEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "PagerDuty DLP alerts",
"url": "https://events.pagerduty.com/v2/enqueue",
"events": ["dlp_trigger", "quota_exceeded"],
"secret": "s3cr3t-hmac-key-min8",
"enabled": true
}'

Response 201 Created

{
"id": "7b8c9d0e-1f2a-3b4c-5d6e-7f8a9b0c1d2e",
"name": "PagerDuty DLP alerts",
"url": "https://events.pagerduty.com/v2/enqueue",
"events": ["dlp_trigger", "quota_exceeded"],
"secret": "s3cr3t-hmac-key-min8",
"enabled": true,
"created_at": "2026-03-12T15:00:00Z",
"updated_at": "2026-03-12T15:00:00Z"
}

PUT /api/admin/webhooks/{webhook_id}

Partial update — only the fields included in the request body are modified. Omit secret to leave the signing key unchanged.

Path parameters

ParameterTypeDescription
webhook_idUUIDThe webhook UUID

Request — disable a webhook

Terminal window
curl -s -X PUT \
https://gateway.arbitex.ai/api/admin/webhooks/7b8c9d0e-1f2a-3b4c-5d6e-7f8a9b0c1d2e \
-H "Authorization: Bearer $ARBITEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'

Request — rotate the signing secret

Terminal window
curl -s -X PUT \
https://gateway.arbitex.ai/api/admin/webhooks/7b8c9d0e-1f2a-3b4c-5d6e-7f8a9b0c1d2e \
-H "Authorization: Bearer $ARBITEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"secret": "new-signing-secret-rotated"}'

Request — add an event type

Terminal window
curl -s -X PUT \
https://gateway.arbitex.ai/api/admin/webhooks/7b8c9d0e-1f2a-3b4c-5d6e-7f8a9b0c1d2e \
-H "Authorization: Bearer $ARBITEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"events": ["dlp_trigger", "quota_exceeded", "bundle_state_change"]}'

Response 200 OK — the updated webhook object (without secret).

Response 404 Not Found — webhook ID does not exist within the tenant.


DELETE /api/admin/webhooks/{webhook_id}

Permanently removes the webhook registration and stops all future deliveries.

Request

Terminal window
curl -s -X DELETE \
https://gateway.arbitex.ai/api/admin/webhooks/7b8c9d0e-1f2a-3b4c-5d6e-7f8a9b0c1d2e \
-H "Authorization: Bearer $ARBITEX_API_KEY"

Response 204 No Content


POST /api/admin/webhooks/{webhook_id}/test

Sends a synthetic webhook_test event to the webhook URL immediately. Use this after initial registration, after rotating the signing secret, or after infrastructure changes that may affect reachability.

Request

Terminal window
curl -s -X POST \
https://gateway.arbitex.ai/api/admin/webhooks/7b8c9d0e-1f2a-3b4c-5d6e-7f8a9b0c1d2e/test \
-H "Authorization: Bearer $ARBITEX_API_KEY"

Response 200 OK

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

On failure:

{
"success": false,
"status_code": null,
"latency_ms": 10003,
"error": "Connection timed out after 10000ms"
}
FieldTypeDescription
successboolWhether the endpoint returned a 2xx response
status_codeint | nullHTTP status code from the endpoint, or null on connection failure
latency_msint | nullRound-trip time in milliseconds, or null on connection failure
errorstring | nullHuman-readable error description, or null on success

Each entry in recent_deliveries has the following fields:

FieldTypeDescription
idUUIDDelivery attempt UUID
webhook_idUUIDParent webhook UUID
event_typestringThe WebhookEventType that triggered the delivery
payloadobjectFull JSON payload sent to the endpoint
response_statusint | nullHTTP status code returned by the endpoint, or null on connection failure
response_bodystring | nullFirst 1,000 characters of the endpoint response body
successbooltrue if any attempt received a 2xx response
attempt_countintNumber of delivery attempts made (1–3)
delivered_atdatetime | nullTimestamp of first successful delivery, or null if not yet delivered
created_atdatetimeTimestamp of the initial delivery attempt

The gateway POSTs JSON to the webhook URL. Each delivery includes three request headers:

HeaderDescription
X-Arbitex-Webhook-IdThe UUID of the webhook registration
X-Arbitex-Delivery-IdThe UUID of this specific delivery attempt
X-Arbitex-Signaturesha256=<HMAC-SHA256 hex digest of the raw request body>

All events share the same outer envelope:

{
"event_type": "dlp_trigger",
"payload": {
"request_id": "req_01abc123",
"user_id": "usr_01def456",
"rule_id": "ssn-redact",
"entity_type": "ssn",
"action_taken": "REDACT",
"timestamp": "2026-03-12T10:15:00Z"
},
"timestamp": "2026-03-12T10:15:00.482Z"
}

The outer timestamp is the delivery time. payload.timestamp (when present) is the event time. They may differ slightly due to processing latency.

new_conversation example:

{
"event_type": "new_conversation",
"payload": {
"conversation_id": "conv_01abc123",
"user_id": "usr_01def456",
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"created_at": "2026-03-12T10:00:00Z"
},
"timestamp": "2026-03-12T10:00:00.198Z"
}

quota_exceeded example:

{
"event_type": "quota_exceeded",
"payload": {
"user_id": "usr_01def456",
"group_id": null,
"limit_type": "monthly_token_limit",
"limit_value": 2000000,
"current_usage": 2001450,
"reset_at": "2026-04-01T00:00:00Z"
},
"timestamp": "2026-03-12T11:42:07.321Z"
}

bundle_state_change example:

{
"event_type": "bundle_state_change",
"payload": {
"bundle_id": "bundle-uuid-...",
"bundle_name": "PCI-DSS Baseline",
"previous_state": "active",
"new_state": "archived",
"changed_by": "admin-uuid-...",
"changed_at": "2026-03-12T09:00:00Z"
},
"timestamp": "2026-03-12T09:00:00.544Z"
}

The X-Arbitex-Signature header value is sha256= followed by the hex-encoded HMAC-SHA256 of the raw request body bytes using the webhook’s secret.

Always verify the signature on raw body bytes before parsing the JSON. Parsing first can allow a forged payload to pass verification if your JSON library normalizes the input.

import hmac
import hashlib
def verify_arbitex_signature(
raw_body: bytes,
secret: str,
signature_header: str,
) -> bool:
"""
Verify an Arbitex webhook signature.
Args:
raw_body: The raw, unmodified request body bytes.
secret: The signing secret configured on the webhook.
signature_header: The value of the X-Arbitex-Signature header.
Returns:
True if the signature is valid, False otherwise.
"""
prefix = "sha256="
if not signature_header.startswith(prefix):
return False
received_digest = signature_header[len(prefix):]
expected_digest = hmac.new(
key=secret.encode("utf-8"),
msg=raw_body,
digestmod=hashlib.sha256,
).hexdigest()
# Use compare_digest to prevent timing-based attacks.
return hmac.compare_digest(expected_digest, received_digest)
# Example: Flask handler
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "s3cr3t-hmac-key-min8"
@app.route("/arbitex-events", methods=["POST"])
def handle_webhook():
sig = request.headers.get("X-Arbitex-Signature", "")
webhook_id = request.headers.get("X-Arbitex-Webhook-Id", "")
delivery_id = request.headers.get("X-Arbitex-Delivery-Id", "")
if not verify_arbitex_signature(request.get_data(), WEBHOOK_SECRET, sig):
abort(403)
event = request.get_json()
print(f"[{delivery_id}] webhook={webhook_id} event={event['event_type']}")
return "", 200

Use hmac.compare_digest rather than == for the final comparison — constant-time comparison prevents timing-based attacks that could leak the expected digest.


StatusCondition
400 Bad RequestInvalid request body, unsupported event_type, or secret shorter than 8 characters
403 ForbiddenCaller is not an admin
404 Not FoundWebhook ID does not exist within the tenant