Skip to main content

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_idReceives
A key idEvents 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:

SelectorMatches
envelope.completedExactly 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

RoutePurpose
GET /api/v2/webhooks/endpointsList endpoints. Optional ?partner_key_id= filter
POST /api/v2/webhooks/endpointsCreate; returns the secret once
PATCH /api/v2/webhooks/endpoints/:endpoint_idUpdate url, events, description, or active state
DELETE /api/v2/webhooks/endpoints/:endpoint_idDisable. Delivery history is retained
POST /api/v2/webhooks/endpoints/:endpoint_id/rotate-secretIssue a new secret
POST /api/v2/webhooks/endpoints/:endpoint_id/testSend a webhook.test delivery
GET /api/v2/webhooks/endpoints/:endpoint_id/deliveriesDelivery log
GET /api/v2/webhooks/deliveries/:delivery_idOne delivery, including the payload
POST /api/v2/webhooks/deliveries/:delivery_id/redeliverReplay a delivery
GET /api/v2/webhooks/eventsThe 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 secret
  • X-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