Webhooks
Webhooks notify your application in real time when things happen in Loyva. Instead of polling, you receive signed HTTP POST requests at URLs you register.
Most integrations start with one event: envelope.completed,
which fires when every party has signed and carries the values that were filled into the
document.
Setup
Register endpoints in Settings → Developers → Webhooks, or over the API:
curl -X POST https://api.loyva.com/api/v2/webhooks/endpoints \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/loyva",
"description": "Production CRM sync",
"events": ["envelope.completed", "vault.*"]
}'
The signing secret is returned once, on creation. Store it immediately.
An organization can register several endpoints, each subscribing to a different set of events — see Managing endpoints.
Webhooks used to be a single webhook_url on a partner API key. Those configurations were
migrated to endpoints automatically and subscribed to *, so existing integrations keep
receiving exactly what they did before. PATCH /api/v2/partner/webhook still works and
writes through to the migrated endpoint. New integrations should use the endpoints API.
Payload format
Every body is JSON with the same envelope:
{
"event_type": "vault.stored",
"event_id": "evt_9f3a2b1c4d5e6f7a8b9c0d1e",
"org_id": "org_abc123",
"envelope_id": "env_x7k9m2p4q1w3",
"timestamp": "2026-08-27T10:15:00.000Z",
"data": {
"path": "ucc-vault/org_abc123/env_x7k9m2p4q1w3/signed.pdf",
"hash": "a1b2c3d4..."
}
}
| Field | Type | Description |
|---|---|---|
event_type | string | One of the documented event types |
event_id | string | Unique event identifier, stable across retries and replays |
org_id | string | Organization the event belongs to |
envelope_id | string | null | Envelope id, null for events not scoped to one |
timestamp | string | ISO 8601 event time |
data | object | Event-specific payload |
When several endpoints subscribe to the same event they all receive the same
event_id — it identifies the event, not the delivery.
Headers
| Header | Description |
|---|---|
Content-Type | application/json |
User-Agent | Loyva-Webhooks/1.0 |
X-Loyva-Event | Event type, mirroring event_type |
X-Loyva-Delivery | Unique delivery id, for correlating retries |
X-Loyva-Endpoint | The endpoint id this delivery targeted |
X-Loyva-Signature | sha256=<hex> HMAC-SHA256 of the raw body |
X-Loyva-Signature-Previous | Present only during a 48h secret rotation window |
X-Loyva-Signature is sent whenever the endpoint has a signing secret. Endpoints migrated
from the older per-key configuration may not have one — the console flags those as
Not signed. See rotating a secret.
Responding to webhooks
- Return
2xxto acknowledge - Loyva retries on non-2xx or network errors with exponential backoff: 1 min, 5 min, 30 min, 2 hr, 8 hr (5 attempts total, ±20% jitter)
- Process asynchronously — acknowledge immediately, work in the background
- Deduplicate on
event_id— delivery is at-least-once
app.post('/webhooks/loyva', (req, res) => {
// Verify the signature first (see Verification)
res.status(200).json({ received: true })
processWebhookAsync(req.body) // deduplicate on event_id
})
An endpoint you disable stops retrying immediately — in-flight deliveries are marked failed rather than continuing to retry for hours.
Delivery log
Every attempt is recorded with its status, response code and body, attempt count, and
duration. Browse it in the console under Webhooks → Deliveries, or via
GET /api/v2/webhooks/endpoints/:endpoint_id/deliveries. Any delivery can be
replayed.
Next steps
- Managing endpoints — creating endpoints and choosing events
- Events — the complete catalog
- Signed document field data — the
envelope.completedpayload - Verification — verifying signatures