Webhook troubleshooting
This recipe shows how to diagnose webhook delivery failures and replay failed deliveries. FVI's webhook system uses HMAC signatures, automatic retries with exponential backoff, and a dead-letter queue for permanently failed deliveries.
Endpoints
GET /v1/webhooks # List webhooks
GET /v1/webhooks/{id}/deliveries # List recent deliveries
POST /v1/webhooks/{id}/deliveries/{deliveryId}/replay # Replay a failed delivery
GET /v1/webhooks/{id}/dlq # List dead-letter queue entries
Prerequisites
- A valid
tenantIdand bearer JWT. - The user must have the
tenant_adminrole.
Webhook delivery flow
List recent deliveries
curl -X GET "https://api.falconveritas.com/v1/webhooks/webhook-001/deliveries?limit=50&status=failed" \
-H "Authorization: Bearer $FVI_JWT" \
-H "X-Tenant-Id: $FVI_TENANT_ID" \
-H "Content-Type: application/json"
Response
{
"items": [
{
"id": "delivery-001",
"webhook_id": "webhook-001",
"event_type": "exception.sla.breached",
"event_id": "evt-12345",
"attempt_count": 3,
"max_attempts": 3,
"status": "failed",
"last_status_code": 500,
"last_error": "Connection timeout after 30s",
"first_attempted_at": "2026-08-22T15:00:00Z",
"last_attempted_at": "2026-08-22T15:08:30Z",
"next_retry_at": null,
"payload_size_bytes": 1240
}
],
"summary": {
"total": 12450,
"delivered": 12380,
"failed": 50,
"in_flight": 20
}
}
Replay a delivery
curl -X POST https://api.falconveritas.com/v1/webhooks/webhook-001/deliveries/delivery-001/replay \
-H "Authorization: Bearer $FVI_JWT" \
-H "X-Tenant-Id: $FVI_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "webhook-replay-fvi-$RANDOM"
}'
The replay creates a new delivery attempt with the same payload as the original, but resets the retry counter and the HMAC signature timestamp.
List dead-letter queue
curl -X GET "https://api.falconveritas.com/v1/webhooks/webhook-001/dlq" \
-H "Authorization: Bearer $FVI_JWT" \
-H "X-Tenant-Id: $FVI_TENANT_ID" \
-H "Content-Type: application/json"
Common failure causes
| Error | Cause | Remediation |
|---|---|---|
connection_timeout | Target URL is slow or unreachable | Increase timeout, check firewall rules |
tls_handshake_failed | Invalid or expired TLS certificate on target | Renew certificate, verify chain |
4xx_client_error | Target rejected the payload | Inspect payload format and authentication |
5xx_server_error | Target had an internal error | Check target server logs |
payload_too_large | Payload exceeded the target's body limit | Increase target body limit or split events |
invalid_signature | Target rejected the HMAC signature | Verify the signing secret is correct |
HMAC signature verification
FVI signs each webhook payload with HMAC-SHA256. The signature is sent in the X-FVI-Signature header:
X-FVI-Signature: t=1692720000,v1=5257a869e7e3b4d3f9eaa6e1b87c8d9e8b7c8d9e8b7c8d9e8b7c8d9e8b7c8d9e
To verify the signature:
- Read the timestamp
tand the signaturev1from the header. - Concatenate the timestamp and the raw request body:
t.body. - Compute HMAC-SHA256 with your webhook secret.
- Compare the computed signature to the
v1value. Use a constant-time comparison to avoid timing attacks. - Reject the request if the timestamp is more than 5 minutes old (replay protection).
Test a webhook
Use the built-in test event to verify your endpoint is reachable and correctly verifying signatures:
curl -X POST https://api.falconveritas.com/v1/webhooks/webhook-001/test \
-H "Authorization: Bearer $FVI_JWT" \
-H "X-Tenant-Id: $FVI_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"event_type": "webhook.test",
"idempotency_key": "webhook-test-fvi-$RANDOM"
}'
Use cases
- Diagnosing delivery failures — find out why a webhook is failing.
- Recovering from target outages — replay deliveries after the target recovers.
- Verifying signature handling — test your signature verification code.
- Cleaning up dead-letter queue — replay or discard permanently failed deliveries.
Code samples
TypeScript
import { FVI } from '@falconveritas/sdk';
const fvi = new FVI({
baseUrl: 'https://api.falconveritas.com',
bearer: process.env.FVI_JWT!,
tenantId: process.env.FVI_TENANT_ID!,
});
// List failed deliveries
const failed = await fvi.webhooks.listDeliveries('webhook-001', {
status: 'failed',
limit: 50,
});
console.log(`Failed deliveries: ${failed.items.length}`);
// Replay each failed delivery
for (const delivery of failed.items) {
await fvi.webhooks.replayDelivery('webhook-001', delivery.id, {
idempotencyKey: `webhook-replay-fvi-${Date.now()}`,
});
console.log(`Replayed: ${delivery.id}`);
}
Python
from falconveritas import FVI
client = FVI(
bearer=os.environ["FVI_JWT"],
tenant_id=os.environ["FVI_TENANT_ID"],
)
failed = client.webhooks.list_deliveries("webhook-001", status="failed", limit=50)
print(f"Failed deliveries: {len(failed.items)}")
for delivery in failed.items:
client.webhooks.replay_delivery(
"webhook-001",
delivery.id,
idempotency_key=f"webhook-replay-fvi-{int(time.time())}",
)
print(f"Replayed: {delivery.id}")
Related articles
Last updated: 2026-08-22 · Page owner: Documentation · Source: src/lib/api/webhooks.ts.