Webhooks
Receive real-time event notifications via HTTP callbacks.
Overview
Instead of polling for status updates, register webhook endpoints to receive push notifications when events occur. Overvoid delivers a signed HTTP POST to your endpoint for each event.
Register an Endpoint
curl -X POST https://api.overvoid.io/v1/developer/webhook-endpoints \-H "Authorization: Bearer cusd_test_YOUR_KEY" \-H "Content-Type: application/json" \-d '{"url": "https://your-app.com/webhooks/cusd","event_types": ["alert.created","case.status_changed","sar.filed","compliance.status_changed"]}'
The response includes a signing_secret — save it to verify incoming webhooks.
Event Payload
Each webhook delivery sends a JSON payload:
{"id": "evt_01HXYZ...","type": "alert.created","created_at": "2026-03-06T21:00:00Z","data": {"alert_id": "alert_01HXYZ...","entity_id": "ent_01HXYZ...","rule_id": "rule_structuring","severity": "high","description": "Possible structuring: 5 transactions totaling $9,800 in 24h"}}
Verifying Signatures
Every webhook includes two headers for HMAC verification:
x-cusd-signature— HMAC-SHA256 hex digestx-cusd-timestamp— Unix timestamp (seconds)
The signed payload is {timestamp}.{body}. Verify like this:
import hmac, hashlib, timedef verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool:# Reject if timestamp is more than 5 minutes oldif abs(time.time() - int(timestamp)) > 300:return Falseexpected = hmac.new(secret.encode(),f"{timestamp}.".encode() + body,hashlib.sha256,).hexdigest()return hmac.compare_digest(expected, signature)
import crypto from "crypto";function verifyWebhook(body, signature, timestamp, secret) {if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {return false;}const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");return crypto.timingSafeEqual(Buffer.from(expected),Buffer.from(signature),);}
Event Types
| Category | Events |
|---|---|
| Alert | alert.created alert.disposition_set |
| Case | case.created case.status_changed case.assigned |
| Filing | sar.filed ctr.filed |
| Entity | entity.risk_score_changed |
| Screening | fincen_314a.match_found adverse_media.detected |
| Compliance | compliance.status_changed compliance.wallet_flagged compliance.entity_suspended |
| Transfer | transfer.detected transfer.confirmed |
| Account | account.approved account.suspended account.kyb_expired |
Example Payloads
Below are example payloads for key compliance event types.
alert.created
Fired when a monitoring rule triggers a new compliance alert.
{"id": "evt_01J8QXYZ...","type": "alert.created","created_at": "2026-03-24T14:30:00Z","data": {"alert_id": "alert_01J8QXYZ...","entity_id": "ent_01HXYZ...","rule_id": "rule_structuring","rule_name": "Structuring Detection","severity": "high","description": "Possible structuring: 5 transactions totaling $9,800 in 24h","transaction_ids": ["tx_01A...", "tx_01B...", "tx_01C..."],"created_at": "2026-03-24T14:30:00Z"}}
case.status_changed
Fired when an investigation case transitions status (e.g. open to under_review, under_review to escalated).
{"id": "evt_01J9ABCD...","type": "case.status_changed","created_at": "2026-03-24T16:45:00Z","data": {"case_id": "case_01J9ABCD...","entity_id": "ent_01HXYZ...","previous_status": "open","new_status": "under_review","assigned_to": "analyst@example.com","alert_ids": ["alert_01J8QXYZ..."],"updated_at": "2026-03-24T16:45:00Z"}}
sar.filed
Fired when a Suspicious Activity Report is marked as filed with FinCEN.
{"id": "evt_01JAEFGH...","type": "sar.filed","created_at": "2026-03-24T18:00:00Z","data": {"sar_id": "sar_01JAEFGH...","case_id": "case_01J9ABCD...","entity_id": "ent_01HXYZ...","bsa_id": "31000012345678","filing_date": "2026-03-24","amount_involved": 15000.00,"activity_start_date": "2026-02-01","activity_end_date": "2026-03-20"}}
Retry Policy
Failed deliveries (non-2xx response or timeout) are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 5 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 2 minutes |
| 4th retry | 15 minutes |
| 5th retry | 1 hour |
| 6th retry | 4 hours |
After 6 failed retries (~5.5 hours), the delivery is marked as permanently failed. You can manually retry from the Webhooks page or via the API.
Notifications (Polling Alternative)
Poll for compliance events instead of receiving webhooks. Useful for integrations that cannot receive inbound HTTP requests or need a simpler integration path.
curl "https://api.overvoid.io/v1/monitoring/notifications?unread_only=false&limit=20" \-H "Authorization: Bearer cusd_test_YOUR_KEY"
Query parameters:
| Param | Type | Description |
|---|---|---|
unread_only | boolean | If true, only return unread notifications (default false) |
limit | int | Max results (default 20, max 100) |
Response:
{"notifications": [{"id": "ntf_01HXYZ...","type": "alert_created","title": "CRITICAL: Risk Tier Threshold Breach","body": "Phantom Shell Ltd triggered a critical alert...","read": false,"entity_id": "ent_01HXYZ...","alert_id": "alt_01HXYZ...","case_id": null,"created_at": "2026-03-25T19:00:00Z"}],"unread_count": 4}
Notification types:
| Type | Description |
|---|---|
alert_created | A monitoring rule triggered a new alert |
case_assigned | A case was assigned to an investigator |
sla_breach | An alert or case exceeded its SLA deadline |
sar_filed | A SAR was filed with FinCEN |
entity_status_changed | An entity's compliance status changed |
screening_complete | A sanctions or adverse media screening finished |
Mark Notification as Read
Mark a single notification as read to track which events have been processed by your integration.
curl -X POST https://api.overvoid.io/v1/monitoring/notifications/{notification_id}/read \-H "Authorization: Bearer cusd_test_YOUR_KEY"
Best Practices
- Always verify signatures — Never trust webhook payloads without HMAC verification.
- Respond quickly — Return a 2xx within 5 seconds. Process the event asynchronously if needed.
- Handle duplicates — Use the
idfield for idempotency. The same event may be delivered more than once. - Use HTTPS — Webhook URLs must use HTTPS. HTTP endpoints are rejected.