What they are
A webhook is an HTTPS endpoint of yours that the API POSTs to when an event occurs in your store (an order completed, a recharge approved, a customer registered). Removes the need to poll the API.
Register an endpoint
POST /api/v1/external/webhooks · scope webhooks:manage
Body
| Field | Type | Description |
|---|---|---|
url | string | HTTPS URL of your endpoint. Required. |
description | string | Internal label (max 200). Optional. |
events | array | List of subscribed events. At least one. |
Request
curl -X POST https://api.nuvlyx.com/api/v1/external/webhooks \
-H "Authorization: Bearer nvl_live_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.my-erp.com/hooks/nuvlyx",
"description": "Production ERP",
"events": ["order.completed", "recharge.approved"]
}' Response 201
{
"id": "wep_…",
"url": "https://api.my-erp.com/hooks/nuvlyx",
"events": ["order.completed", "recharge.approved"],
"status": "ACTIVE",
"secret": "a8f3c8e1b7d4...64-hex-chars...",
"createdAt": "2026-05-18T..."
} The secret is returned ONLY ONCE. Store it in a safe
place — it's used to verify the signature of every event you receive.
Event catalog
| Event | When it fires |
|---|---|
order.created | Order created in PENDING or PROCESSING status. |
order.completed | Order closed with all items DELIVERED. |
order.items_fulfilled | Backfill of PENDING items after stock upload. |
recharge.approved | Wallet recharge approved. |
recharge.rejected | Recharge rejected by OWNER or the system. |
customer.registered | New customer created in the store. |
stock.low | A variation crosses the low-stock threshold. |
stock.available | A previously-zero variation received stock. |
Payload shape
{
"id": "evt_a4f2c8e1b3d7...",
"type": "order.completed",
"created_at": "2026-05-18T15:42:00Z",
"livemode": true,
"data": {
// event-specific payload
}
} Verify the signature
Every POST includes an X-Nuvlyx-Signature header (Stripe-style):
X-Nuvlyx-Signature: t=1747754520,v1=<hmac_sha256_hex>
To verify: concatenate {t}.{rawBody}, compute HMAC-SHA256 with your
secret and compare against v1 with a constant-time comparison.
Node.js
import crypto from 'node:crypto';
export function verifyNuvlyxSignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1)
);
} Python
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split('=') for p in header.split(','))
expected = hmac.new(
secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, parts['v1']) Retries and suspension
- Your endpoint must respond
2xxwithin 8 seconds. - On timeout, network error, or
5xx: up to 3 automatic retries (RabbitMQ backoff). - After 5 consecutive failures, the endpoint is automatically marked
SUSPENDEDand stops receiving events. Re-create it to reactivate. - The first
2xxdelivery resets the failure counter to zero.
Deduplication
The X-Nuvlyx-Event-Id header carries the same id as the
payload. Persist it and reject duplicates — events can be delivered more than once
on retries.
Audit deliveries
GET /api/v1/external/webhooks/:id/deliveries returns the last 50 attempts with status, latency and error message.
Delete an endpoint
DELETE /api/v1/external/webhooks/:id