Skip to main content

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.

Upgrading from per-key webhooks

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..."
}
}
FieldTypeDescription
event_typestringOne of the documented event types
event_idstringUnique event identifier, stable across retries and replays
org_idstringOrganization the event belongs to
envelope_idstring | nullEnvelope id, null for events not scoped to one
timestampstringISO 8601 event time
dataobjectEvent-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

HeaderDescription
Content-Typeapplication/json
User-AgentLoyva-Webhooks/1.0
X-Loyva-EventEvent type, mirroring event_type
X-Loyva-DeliveryUnique delivery id, for correlating retries
X-Loyva-EndpointThe endpoint id this delivery targeted
X-Loyva-Signaturesha256=<hex> HMAC-SHA256 of the raw body
X-Loyva-Signature-PreviousPresent 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 2xx to 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