Data webhooks
Send spans, traces, scores, and billing events to your own endpoint.
A data webhook sends a JSON POST to your URL each time an event happens in Respan, such as a span being logged or your credit balance running low. Use it to copy spans into your own warehouse or to trigger alerts in your systems.
To get alerts when a metric crosses a threshold, use a monitor instead. Monitors have their own webhook destinations.
Create a webhook
Open Webhooks
Go to Settings › Integrations, open the Webhooks tab, and click Create webhook.
Fill in the webhook
Enter a Name and the full endpoint URL. Pick an Event type, choose the API key whose traffic it covers, and click Create.


Copy the secret
The dialog now shows the Webhook secret. Copy it and store it on your server, so you can verify deliveries.
To see it again later, click the webhook and click the eye icon next to Webhook secret. The refresh icon rotates it, and the old secret stops working right away.
Use the Status switch in the list to pause a webhook. To delete one, open the row’s menu and click Delete.
Event types
| Event type | Sent when | event_type in the payload |
|---|---|---|
| span | A span is logged | request_log |
| completed trace | A trace finishes | trace_completed |
| score | An evaluator score is recorded | on_eval_result_ingested |
| User budget limit reached | One of your users goes over their budget | customer_budget_limit_reached |
| Credit low balance threshold reached | Your credit balance drops below the warning amount on Billing | credit_low_balance_threshold_reached |
Events that come from requests only include traffic sent with the webhook’s API key. Create one webhook per event type.
What Respan sends
Each delivery is a POST with a JSON body: the event type, plus the event’s data. For a span, data is the span as Respan logged it, with fields such as model, prompt_tokens, completion_tokens, cost, latency, and customer_identifier.
{
"event_type": "credit_low_balance_threshold_reached",
"data": {
"organization_id": "5f1d2c7e-8a41-4d07-9c52-3b6e95a0c1d4",
"organization_name": "Acme",
"current_balance": 48.72,
"warning_threshold": 50.0,
"timestamp": "2026-09-30T17:24:05.312481+00:00"
}
}Each request carries these headers:
| Header | Value |
|---|---|
X-Keywordsai-Signature | Hex HMAC-SHA256 of the raw request body, keyed with your webhook secret |
Keywordsai-Timestamp | When the delivery was sent, in Unix seconds |
X-Signature-Secret-Type | wh_secret |
Check the signature against the raw body before you parse it:
import hashlib
import hmac
import os
def is_from_respan(raw_body: bytes, signature: str) -> bool:
secret = os.environ["RESPAN_WEBHOOK_SECRET"].encode()
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Delivery
- Return a
2xxwithin 30 seconds. Queue slow work and respond first. - Failed deliveries aren’t retried. After 5 failures in a row, Respan pauses the webhook for 5 minutes and emails your organization’s owner. Events during the pause are dropped.
- The URL must be publicly reachable. Private and internal addresses are blocked.
- A Slack incoming webhook URL (
hooks.slack.com) gets a formatted Slack message instead of the raw JSON.