Webhook Debugging
Monitor, test, and troubleshoot webhook deliveries from the Safravo dashboard.
Sending Test Events
You can instantly verify connectivity and signature validation by sending a test event directly from the dashboard:
- Go to Settings → Webhooks.
- Locate the endpoint in the list and click the Test button.
- A
webhook.pingevent will be dispatched immediately. - Click the Logs button to see the result, including the HTTP status code and response body your server returned.
This simulates a real webhook delivery using the exact same infrastructure, making it the fastest way to debug signature and connectivity issues.
Endpoint Verification
When you create or update a webhook endpoint, Safravo performs a synchronous verification check:
- Safravo sends a
POSTrequest with awebhook.pingevent to your URL. - Your server must respond with a
2xxHTTP status code within 5 seconds. - If the request fails, times out, or returns an error status (e.g.,
404,500), the endpoint will not be saved.
Ensure your server is running, publicly accessible via HTTPS, and ready to receive requests before adding the endpoint in the dashboard.
Accessing the monitoring panel
Every webhook endpoint has a dedicated monitoring panel in the dashboard.
- Go to Settings → Webhooks.
- Click Logs on the endpoint you want to inspect.
Delivery logs
The Logs tab shows the 50 most recent delivery attempts for that endpoint, sorted newest-first. Each entry includes:
| Field | Description |
|---|---|
| Event type | e.g. message.created |
| Status | HTTP response code returned by your server |
| Timestamp | When the delivery was attempted |
| Latency | How long your server took to respond |
Logs expire automatically after 30 days.
Troubleshooting common errors
401 Unauthorized
Your signature verification logic is rejecting the request.
- Check your secret — confirm the
WEBHOOK_SECRETin your environment matches the secret shown when you created the endpoint in the dashboard. The secret is inwhsec_<64 hex chars>format. - Use the raw body — compute the HMAC against the raw request body bytes, before any JSON parsing. See Security → Webhook Signature Verification.
500 Internal Server Error
Your server received the event but threw an exception while processing it.
- Check your application logs for stack traces.
- Respond immediately — Safravo waits up to 10 seconds for a
2xxresponse. If your processing takes longer, acknowledge the webhook immediately with200 OKand move the work to a background queue.
Connection refused / Timeout
Safravo cannot reach your server.
- Public access — your endpoint must be reachable from the public internet.
localhostURLs will not work in production. - Firewall rules — check that your server is not blocking inbound requests from Safravo's IP ranges.
- Local testing — if testing locally, use ngrok or Cloudflare Tunnel to expose your local server. See Testing Locally below.
Circuit breaker (auto-disable)
Safravo uses an automatic circuit breaker to prevent overwhelming failing endpoints.
| Status | Condition |
|---|---|
active | Receiving events normally |
failing | 5 or more consecutive delivery failures — investigate immediately |
disabled | 25 consecutive failures — auto-disabled, manual re-enable required |
The failure counter resets to zero on any successful delivery, so a brief outage will not permanently penalise a healthy endpoint.
When an endpoint is auto-disabled, Safravo sends an email notification to the workspace owner.
Re-enabling a disabled endpoint
- Fix the issue on your server.
- Go to Settings → Webhooks and find the disabled endpoint.
- Click Re-enable.
- The first delivery attempt is delayed by 5 minutes as a cooldown.
Example Payload
When an event occurs, Safravo sends a POST request with a JSON body similar to the one below. Use the type field to determine how to process the data object.
{
"id": "evt_1778293944479_wfway",
"object": "event",
"type": "message.created",
"api_version": "v1",
"created": "2026-05-09T02:32:25.364Z",
"workspaceId": "69ea13a14bcf38af4b8670fb",
"data": {
"message": {
"id": "69fe9cb887ab45e4342c6c60",
"direction": "inbound",
"status": "delivered",
"type": "text",
"content": "Hello",
"createdAt": "2026-05-09T02:32:24.441Z"
}
}
}Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for this event. Use this to prevent duplicate processing. |
object | string | Always event. |
type | string | The event type (e.g., message.created, message.updated). |
api_version | string | The version of the webhook payload schema. |
created | string | ISO 8601 timestamp of when the event was generated. |
workspaceId | string | The unique ID of the workspace where the event occurred. |
data | object | The actual resource associated with the event (e.g., the message object). |
Security
Safravo signs all webhook events to allow you to verify that they originated from us.
Signature Verification
Each request includes an X-Safravo-Signature header. This is a HMAC-SHA256 signature generated using your endpoint's Secret.
- Retrieve the signature from the
X-Safravo-Signatureheader. - Generate a HMAC-SHA256 signature of the raw request body using your secret.
- Compare your generated signature with the one in the header.
Preventing Replay Attacks
Each request also includes an X-Safravo-Timestamp header. We recommend verifying that the timestamp is recent (e.g., within the last 5 minutes) before processing the event to prevent replay attacks.
Delivery retries
Failed deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 (initial) | Immediate |
| 2 | 5 seconds |
| 3 | 30 seconds |
| 4 | 2 minutes |
| 5 | 10 minutes |
| 6–10 | 1 hour each |
After 10 failed attempts, that specific event delivery is marked permanently failed and will not be retried.
Testing locally
Use ngrok or Cloudflare Tunnel to expose your local server during development.
# ngrok
ngrok http 3000
# → https://abc123.ngrok-free.app
# Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000Register the tunnel URL as your webhook endpoint in Settings → Webhooks. Update it whenever the tunnel restarts — the free tier of ngrok generates a new URL on every restart. Use a paid ngrok account or Cloudflare Tunnel for a stable URL.
Cloudflare Tunnel is free and provides a stable URL that persists across restarts, making it a good alternative to ngrok for local development.
Deduplication
Safravo may deliver the same event more than once during retries. Use the X-Safravo-Delivery header as an idempotency key to avoid processing duplicates.
const deliveryId = req.headers['x-safravo-delivery'];
const alreadyProcessed = await redis.get(`webhook:${deliveryId}`);
if (alreadyProcessed) {
return res.status(200).json({ received: true }); // idempotent
}
// ... process event ...
await redis.setex(`webhook:${deliveryId}`, 86400, '1'); // TTL: 24 hours