Webhooks And Notifications
Configure tenant webhook subscriptions, SMTP delivery, retry behavior, and notification safety controls.
Webhooks And Notifications
The backend has a notification worker that sends SMTP messages and signed
webhook deliveries from the notifications.to_send queue. Invitations and
billing export delivery metadata are currently the most visible email flows;
certificate webhooks are emitted for certificate.issued.
Webhook Routes
| Action | Route | Role |
|---|---|---|
| List subscriptions | GET /api/v1/webhooks | CLIENT_ADMIN |
| Create subscription | POST /api/v1/webhooks | CLIENT_ADMIN |
| Delete subscription | DELETE /api/v1/webhooks/{id} | CLIENT_ADMIN |
Example:
{
"url": "https://hooks.example.com/wipe",
"events": ["certificate.issued"]
}
The subscription secret is returned once on creation. Store it in the receiving system immediately.
Delivery Security
| Control | Behavior |
|---|---|
| SSRF policy | Webhook URLs pass the outbound URL policy and reject private, loopback, link-local, credential-bearing, and unsupported URLs by default. |
| Signature | Webhook body is signed with X-Wipe-Signature: sha256=<hmac>. |
| Secret custody | The webhook secret is generated by the backend and returned once. |
| Retry | Notification worker retries with backoff and writes DLQ metadata after exhaustion. |
Use SECURITY_OUTBOUND_ALLOWED_HOSTS for known webhook hosts. Use private-network
overrides only in controlled internal deployments.
Notification Worker
The worker command is:
/app/worker notifications
Relevant variables:
| Variable | Use |
|---|---|
NOTIFICATION_TRANSPORT | disabled or SMTP/webhook-capable delivery mode. |
NOTIFICATION_HTTP_TIMEOUT | Outbound webhook timeout. |
NOTIFICATION_SMTP_HOST, NOTIFICATION_SMTP_PORT | SMTP endpoint. |
NOTIFICATION_SMTP_USERNAME, NOTIFICATION_SMTP_PASSWORD | Optional SMTP credentials. |
NOTIFICATION_SMTP_FROM | Envelope/display sender. |
NOTIFICATION_SMTP_TLS | none, starttls, or provider-specific TLS mode. |
NOTIFICATION_SMTP_TIMEOUT | SMTP timeout. |
Local compose sends SMTP to Mailpit on port 1025.
Email Templates
Invitation email templates are organization branding fields:
| Field | Purpose |
|---|---|
email_templates.user_invited_subject | Subject line. |
email_templates.user_invited_text | Plain-text body. |
email_templates.user_invited_html | HTML body. |
The backend renders variables such as {{app_name}}, {{logo_url}},
{{accept_url}}, {{email}}, {{role}}, and {{organization_name}}.
Current Event Coverage
| Event | State |
|---|---|
certificate.issued | Emitted from certificate finalization. |
| Billing export delivery updates | Recorded by the notification worker. |
| Invitation emails | Rendered from branding templates and sent through notification delivery. |
proof.* state events | Subscribable direction exists, but producers for failed/rejected/awaiting-license transitions remain a tracked backend gap. |
Triage
| Symptom | Check |
|---|---|
| Invitation email missing | NOTIFICATION_TRANSPORT, SMTP settings, notifications.to_send queue, Mailpit/SMTP logs. |
| Webhook not delivered | URL policy rejection, HTTP timeout, target status code, DLQ reason. |
| Receiver rejects signature | Confirm receiver uses the one-time secret and raw request body. |
| Repeated retries | Fix target dependency first; do not replay DLQ until root cause is understood. |