Webhooks
Outbound webhooks let Veriproof push event notifications to your own systems in real time. When an event matches a registered webhook, the platform sends an HTTPS POST request to your endpoint with a signed JSON payload.
Webhook management requires the CustomerAdmin role. All other roles can view registered webhooks.
Event Types
| Event type | Fired when |
|---|---|
alert.triggered | An alert rule threshold is crossed |
alert.resolved | A triggered alert returns below threshold |
session.flagged | A session receives an attention flag |
session.review.approved | A session review decision is approved |
session.review.rejected | A session review decision is rejected |
governance.threshold_breached | Governance score crosses a configured threshold |
gdpr.erasure_completed | A GDPR erasure request has been cryptographically completed |
gdpr.legal_hold_placed | A legal hold is placed on a data subject |
application.purge_completed | An application purge has finished |
quality.score_computed | Session quality scoring result is available |
evidence_package.ready | An evidence package export is ready for download |
Creating a Webhook
Open Webhook Settings
In the Customer Portal, navigate to Settings → Webhooks and click Add webhook.
Enter the endpoint URL
Provide the HTTPS URL of your endpoint. Plain HTTP endpoints are rejected — TLS is required.
Select events
Choose which event types this webhook should receive. You can register multiple webhooks, each subscribed to different events, or a single webhook that receives all events.
(Optional) Add a secret
Enter a signing secret. Veriproof will use this secret to compute an HMAC-SHA256 signature of the request body and include it in the X-Veriproof-Signature header. This allows your endpoint to verify the payload is genuine.
Save
Click Save webhook. The endpoint is immediately active.
Webhook Payload Format
All webhooks use the same envelope:
{
"id": "evt_01J7...",
"type": "alert.triggered",
"timestamp": "2025-06-15T14:30:00Z",
"tenantId": "t_abc123",
"applicationId": "app_xyz789",
"data": {
// Event-specific fields
}
}Example: alert.triggered
{
"id": "evt_01J7abcdef",
"type": "alert.triggered",
"timestamp": "2025-06-15T14:30:00Z",
"tenantId": "t_abc123",
"applicationId": "app_xyz789",
"data": {
"alertRuleId": "rule_01H9...",
"alertRuleName": "High refusal rate (production)",
"metricName": "refusal_rate",
"currentValue": 0.34,
"threshold": 0.25,
"severity": "warning"
}
}Example: gdpr.erasure_completed
{
"id": "evt_01J7ghijkl",
"type": "gdpr.erasure_completed",
"timestamp": "2025-06-15T02:01:05Z",
"tenantId": "t_abc123",
"applicationId": null,
"data": {
"dataSubjectId": "ds_01H5...",
"erasureRequestId": "era_01H6...",
"completedAt": "2025-06-15T02:01:05Z",
"certificateUrl": "https://my.veriproof.app/erasure-certificates/..."
}
}Verifying Signatures
If you set a signing secret, every delivery includes the X-Veriproof-Signature header:
X-Veriproof-Signature: sha256=a3f5c9...To verify the signature in your endpoint handler:
import hmac
import hashlib
def verify_signature(payload_bytes: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode("utf-8"),
payload_bytes,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)import { createHmac, timingSafeEqual } from "crypto";
function verifySignature(payload: Buffer, header: string, secret: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(payload).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}Always use a constant-time comparison function (e.g. hmac.compare_digest in Python, timingSafeEqual in Node.js) when checking signatures. String equality operators (==) are vulnerable to timing attacks.
Delivery and Retry Policy
Veriproof delivers webhooks with the following guarantees:
| Parameter | Value |
|---|---|
| Timeout | 10 seconds per delivery attempt |
| Retry attempts | 3 (on non-2xx response or connection failure) |
| Retry backoff | Exponential: 5s, 25s, 125s |
| Failure handling | After 3 failures the event is marked failed |
Your endpoint must return a 2xx status code within the timeout to acknowledge delivery. Returning any other status code (including 3xx redirects) causes a retry.
Idempotency
Each event has a unique id field. If your endpoint receives the same event ID more than once (due to retries), it should process it idempotently. Store the id and skip re-processing if already handled.
Delivery Logs
Every webhook delivery attempt is logged and visible in the Customer Portal:
- Navigate to Settings → Webhooks.
- Click on a webhook endpoint.
- Click Delivery logs.
The log shows every attempt for the last 7 days, including the HTTP response status, latency, and request/response body.
API
GET /v1/webhooks/{id}/delivery-logsTesting a Webhook
Use the Test button in the portal to send a sample alert.triggered payload to your endpoint and verify it responds correctly, without waiting for a real event.
POST /v1/webhooks/{id}/testManaging Webhooks via API
GET /v1/webhooks # List all webhooks
POST /v1/webhooks # Create a webhook
PUT /v1/webhooks/{id} # Update a webhook
DELETE /v1/webhooks/{id} # Delete a webhook
POST /v1/webhooks/{id}/test # Send a test delivery
GET /v1/webhooks/{id}/delivery-logs # View delivery historyNotification Channels (Slack / Teams)
For human-readable alert notifications in Slack or Microsoft Teams instead of raw webhook payloads, see Notification Channels. These are configured separately under Settings → Notification Channels and use the same underlying event system with a pre-formatted message template.