Managing endpoints
A webhook endpoint is its own object. An organization can register several, each with its own URL, signing secret, and event subscription — so a CRM sync and a billing service can receive completely different slices of the event stream.
Manage endpoints in Settings → Developers → Webhooks, or over the API.
Endpoints and API keys
An endpoint may be attached to a partner API key, or be org-wide:
partner_key_id | Receives |
|---|---|
| A key id | Events for that organization, alongside anything else that key does |
null (org-wide) | Every matching event in the organization, regardless of which key or user triggered it |
An organization may register up to 25 endpoints, at most 10 per API key.
Choosing events
Each endpoint stores a list of selectors. Three forms are accepted:
| Selector | Matches |
|---|---|
envelope.completed | Exactly that event |
envelope.* | Every event in the envelope domain |
* | Every event |
Wildcards are prefix-only — *.completed and envelope.*.x are rejected.
Prefer a wildcard when you want a whole domain. New events added to a domain are
delivered automatically to endpoints subscribed with <domain>.*, whereas an endpoint
listing ids one by one must be edited each time the catalog grows.
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 response returns the signing secret once:
{
"data": {
"endpoint_id": "whep_a1b2c3d4e5f6a7b8c9d0",
"url": "https://your-app.com/webhooks/loyva",
"events": ["envelope.completed", "vault.*"],
"is_active": true,
"secret_set": true,
"secret": "whsec_4f3a...",
"secret_note": "Store this signing secret securely. It will not be shown again."
}
}
Store secret immediately. It is never returned again by any endpoint — not in a list, not
in a read. If you lose it, rotate.
Endpoint routes
| Route | Purpose |
|---|---|
GET /api/v2/webhooks/endpoints | List endpoints. Optional ?partner_key_id= filter |
POST /api/v2/webhooks/endpoints | Create; returns the secret once |
PATCH /api/v2/webhooks/endpoints/:endpoint_id | Update url, events, description, or active state |
DELETE /api/v2/webhooks/endpoints/:endpoint_id | Disable. Delivery history is retained |
POST /api/v2/webhooks/endpoints/:endpoint_id/rotate-secret | Issue a new secret |
POST /api/v2/webhooks/endpoints/:endpoint_id/test | Send a webhook.test delivery |
GET /api/v2/webhooks/endpoints/:endpoint_id/deliveries | Delivery log |
GET /api/v2/webhooks/deliveries/:delivery_id | One delivery, including the payload |
POST /api/v2/webhooks/deliveries/:delivery_id/redeliver | Replay a delivery |
GET /api/v2/webhooks/events | The event catalog |
All require an admin JWT.
DELETE is a soft delete: the endpoint stops receiving events but its delivery log survives
as audit evidence.
URL requirements
Endpoint URLs must be public HTTPS. Loyva rejects loopback and private addresses,
.local / .internal / .lan hosts, and cloud metadata IPs — at create time, at update
time, and again immediately before every delivery. Deliveries do not follow redirects, so a
302 toward an internal address fails rather than being followed.
Rotating a signing secret
curl -X POST https://api.loyva.com/api/v2/webhooks/endpoints/whep_.../rotate-secret \
-H "Authorization: Bearer $JWT"
For 48 hours after a rotation, every delivery is signed with both secrets:
X-Loyva-Signature— the new secretX-Loyva-Signature-Previous— the old secret
So you can deploy the new secret without dropping events. Accept either header during the
window; after grace_expires_at only the new signature is sent.
An endpoint whose secret_set is false delivers unsigned. This applies to endpoints
migrated from the older per-API-key webhook configuration, where a secret was never set.
The console flags these as Not signed — rotate to generate one.
Testing an endpoint
curl -X POST https://api.loyva.com/api/v2/webhooks/endpoints/whep_.../test \
-H "Authorization: Bearer $JWT"
Fires a signed webhook.test event and reports whether your endpoint answered 2xx. The
response deliberately does not include your endpoint's response body.
Delivery log and replay
Every attempt is recorded: status, response code and body, attempt count, duration, and the next retry time.
curl "https://api.loyva.com/api/v2/webhooks/endpoints/whep_.../deliveries?status=failed" \
-H "Authorization: Bearer $JWT"
To replay one:
curl -X POST https://api.loyva.com/api/v2/webhooks/deliveries/wdel_.../redeliver \
-H "Authorization: Bearer $JWT"
A replay is a new delivery row linked by redelivery_of, sent on the next delivery sweep.
The body keeps its original event_id, so a receiver deduplicating on event_id treats
it as the same event — which is what makes replay safe to use liberally.
Next steps
- Events — the full catalog
- Signed document field data — what
envelope.completedcarries - Verification — verifying signatures