Webhook Setup
This page covers the management side. The contract for an incoming delivery (headers, signature, retries) is on its own page: Webhooks.
You can manage endpoints from the panel or the API; both reach the same control plane.
Access requirements
- The Bearer principal must hold the
webhooks:managepermission. - The organization must have the webhook entitlement enabled — otherwise every call returns
403 webhook.entitlement.not_enabled.
Endpoints
All are relative to https://api-gateway.lunixi.com and require a Bearer token.
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/organizations/{orgId}/webhook-endpoints | Create an endpoint |
GET | /api/v1/organizations/{orgId}/webhook-endpoints | List endpoints |
GET | /api/v1/organizations/{orgId}/webhook-endpoints/{endpointId} | One endpoint |
PUT | /api/v1/organizations/{orgId}/webhook-endpoints/{endpointId} | Update |
DELETE | /api/v1/organizations/{orgId}/webhook-endpoints/{endpointId} | Disable |
GET | /api/v1/organizations/{orgId}/webhook-deliveries | Delivery history |
POST | /api/v1/organizations/{orgId}/webhook-deliveries/{deliveryId}/replay | Replay a delivery |
DELETE disables rather than deletes. The endpoint and its delivery history stay on record, to preserve the audit trail. To use the same address again, create a new endpoint.
Endpoint fields
| Field | Meaning |
|---|---|
name | The name you see in the panel (max 160 characters) |
url | Where deliveries go — must be reachable from the public internet |
productCode | Whose events it receives (e.g. PAYMENT_GATEWAY) |
environment | Environment separation — TEST or LIVE; live and test endpoints never mix |
secretRef | Required. The Secret Vault reference of the signing secret: vault:<secretId> — the secret itself never travels through the API |
eventTypes | Required. The subscribed event types; wildcards are supported (e.g. ["payment.*"]) |
status | ACTIVE · FAILED (set automatically after consecutive failures) |
failureCount | Consecutive-failure counter — reset on a successful delivery |
lastSuccessAt / lastFailureAt | Last success / last failure timestamps |
Event subscriptions belong to the endpoint and are per event type; you subscribe an endpoint only to the events it cares about. The DELETE behaviour is unchanged: it disables rather than deletes.
For bank account events, create the endpoint with productCode: "BANK_CONNECT" and eventTypes: ["bank_account.*"]; add "bank_transfer.*" to also receive transfer events. → Bank Accounts › Webhook Events
For insurance events, create the endpoint with productCode: "INSURANCE_SERVICE" and eventTypes: ["insurance.*"]. → Insurance › Webhook Events
Setup flow
Prepare your endpoint
A publicly reachable address that accepts POST. In the first version give it one job: verify the signature, write the event to your own queue, return 2xx. Do not run business logic synchronously — the delivery timeout defaults to 10 seconds.
Create the signing secret
The API does not generate or return a secret — the signing secret lives in the Secret Vault, and the endpoint only references it via secretRef. Two paths:
Panel path. When creating an endpoint, the panel generates a whsec_… secret client-side, stores it in the Secret Vault and sets secretRef automatically. The secret is shown only once — put it in your own vault; if you lose it you cannot read it back, only replace it.
API path. Generate the secret yourself, write it to the vault first, and pass the returned id to the endpoint create call:
curl -X POST "https://api-gateway.lunixi.com/api/v1/secrets" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"merchantId": "<orgId>",
"secretKey": "webhook/<orgId>/LIVE/PAYMENT_GATEWAY/<name>",
"payload": { "signingSecret": "whsec_<your own random value, ≥32 chars>" }
}'You will use the returned secretId as secretRef: "vault:<secretId>". The signing secret must be at least 16 characters — in practice, generate a random value of ≥ 32 characters.
The secret is held server-side in an encrypted vault and resolved at delivery time — it is never returned by list calls. So "I'll look it up later" is not an option.
Create the endpoint
curl -X POST "https://api-gateway.lunixi.com/api/v1/organizations/$ORG_ID/webhook-endpoints" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Payment events — live",
"url": "https://merchant.example.com/webhooks/lunixi",
"productCode": "PAYMENT_GATEWAY",
"environment": "LIVE",
"secretRef": "vault:<secretId>",
"eventTypes": ["payment.*"]
}'environment accepts only TEST or LIVE; secretRef and eventTypes are required.
Write and test verification
Take the verification code from Webhooks. The v2 signature is an HMAC of the raw body — you verify over {eventId}.{timestamp}.{rawBody} without parsing the body.
Go live
After the first real event arrives, confirm it in the delivery history.
Delivery history
curl "https://api-gateway.lunixi.com/api/v1/organizations/$ORG_ID/webhook-deliveries?status=DEAD_LETTER" \
-H "Authorization: Bearer $TOKEN"Filters: endpointId · eventType · status
Each delivery record carries attemptCount, lastErrorCode, lastError, nextRetryAt and deliveredAt. Attempts are also logged individually — you can see which HTTP status you returned on which attempt.
Replaying a failed delivery
curl -X POST "https://api-gateway.lunixi.com/api/v1/organizations/$ORG_ID/webhook-deliveries/$DELIVERY_ID/replay" \
-H "Authorization: Bearer $TOKEN"This is how a DEAD_LETTER delivery is recovered. If your endpoint was down for a period, list the deliveries in that window and replay them in bulk.
A replay is sent with the same x-lunixi-event-id and resets the attempt budget — the delivery re-enters the queue with a fresh 12 attempts. If you keyed your idempotency on that header, you will not re-process an event you already handled — which is the intended behaviour.
Rotating the secret
After you rotate, the platform dual-signs deliveries with both the old and the new secret for 24 hours (the "grace window"): the x-lunixi-signature header carries two v2= items. A verifier that accepts the signature when any v2= item matches — like the example on Webhooks — needs no changes at all. After 24 hours the old secret stops being used.
The sequence is simple:
- Rotate, and write the new value to your vault.
- During the grace window deliveries arrive with both signatures — if your verifier accepts any matching
v2=item, there is no gap. - After 24 hours signing with the old secret stops; nothing further to do on your side.
If your endpoint was disabled
When the consecutive-failure threshold is crossed (default 20) the endpoint's status becomes FAILED and it stops receiving deliveries. After fixing your server, re-enable it from the panel; you can then replay the deliveries that accumulated meanwhile.
Related
- Webhooks — headers, signature, retries, delivery statuses
- Authentication — how you mint the Bearer token