Webhook integration guide
This guide is the technical reference for receiving webhooks: how deliveries are formatted, signed, and sent, and how to build a reliable endpoint. For an overview and the list of supported events, see Webhooks. To create and manage subscriptions in the UI, see Webhooks management via Dashboard.
Set up webhooks
Prerequisites
- Webhooks enabled on your site. Contact Support to enable Webhooks if you do not have it yet.
- An Admin account. All webhook API methods and the Dashboard page require Admin permissions.
- An HTTPS endpoint to receive webhook deliveries.
- An authorization value for the
Authorizationheader, such as a bearer token (JWT) or a basic authentication token.
Security requirements
- Webhook URLs must use HTTPS. Deliveries are never sent to an
http://URL. The API accepts the URL, but nothing is delivered and no error is reported, so always check the scheme. - The authorization value you provide is sent in the
Authorizationheader of every delivery, and it is also the secret used to sign each delivery. See Signature verification.
Configuration steps
Create and manage subscriptions in the Dashboard or through the API:
- Dashboard: See Webhooks management via Dashboard.
- API: The Webhooks API methods are listed in our API Documentation. To script the setup, see Webhook setup script.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /@api/deki/webhooks | List subscriptions (supports limit, offset, active) |
| POST | /@api/deki/webhooks | Create a subscription |
| GET / PUT / DELETE | /@api/deki/webhooks/{id} | Read, update (URL, authorization value, active), or delete a subscription |
| GET | /@api/deki/webhooks/{id}/events | List the event types a subscription receives |
| PUT / DELETE | /@api/deki/webhooks/{id}/events/{eventtype} | Add or remove one event type |
A subscription is created with only webhook-url and authorization-header, and it starts as active. Add event types one at a time afterwards. A subscription with no event types receives nothing.
<webhook>
<webhook-url>https://example.com/expert-webhook</webhook-url>
<authorization-header>Bearer your-jwt-token</authorization-header>
</webhook>
Limits
- Webhook URL: up to 500 characters. Authorization value: up to 512 characters.
- Only the 100 most recently created active subscriptions on a site receive deliveries. Older active subscriptions beyond that number receive nothing.
Webhook delivery
Request format
- Method:
POST - Content-Type:
application/xml; charset=utf-8 - Body: one event per request, as an XML
<event>document. Requests never contain more than one event.
Request headers
Your endpoint receives the following headers with each delivery:
Authorization: Bearer your-jwt-token X-Nexus-Signature-256: 3f1c...e9a2 X-Nexus-Timestamp: 1791984000 X-Nexus-Delivery-Id: 0b6f7a3e-5c1d-4f0e-9a51-2a7c4d8e9b10 X-Nexus-Wiki-Id: your-site-wiki-id X-Nexus-Page-Id: 1234 X-Nexus-Event-Type: Page_Create X-Nexus-Subscription-Id: your-webhook-subscription-guid X-Nexus-Webhook-Url: https://example.com/expert-webhook Content-Type: application/xml; charset=utf-8
| Header | Value |
|---|---|
Authorization | The authorization value stored on the subscription, sent exactly as entered. |
X-Nexus-Signature-256 | HMAC-SHA256 signature as lowercase hexadecimal. There is no sha256= prefix. |
X-Nexus-Timestamp | Unix time in seconds when the delivery was signed. |
X-Nexus-Delivery-Id | A new unique ID for each delivery of an event to a subscription. |
X-Nexus-Wiki-Id | The ID of the site that raised the event. |
X-Nexus-Page-Id | The page that triggered the event. For user events and other events with no page, the header is present with an empty value. |
X-Nexus-Event-Type | The event type of this delivery, for example Page_Create or User_Update. |
X-Nexus-Subscription-Id | The GUID of the subscription that this delivery is for. |
X-Nexus-Webhook-Url | The URL registered on the subscription. |
X-Nexus-Subscription-Id and X-Nexus-Webhook-Url are useful if a single endpoint receives deliveries for multiple subscriptions.
Payload
The body is the event XML. The type attribute uses a lower-case form of the event type (for example page:create or user:update), while the X-Nexus-Event-Type header uses the event type name (Page_Create, User_Update). The id attribute uniquely identifies the event.
Page event (simplified):
<event id="b2d6c0a0-6b9e-11f1-8000-0a1b2c3d4e5f" datetime="2026-10-14T16:00:00Z" type="page:create" wikiid="your-site-wiki-id" language="en-US" version="5">
<request id="...">...</request>
<page id="1234">
<path>Path/To/Page</path>
</page>
</event>
User event (simplified):
<event id="c4f1a2b0-6b9e-11f1-8000-0a1b2c3d4e5f" datetime="2026-10-14T16:00:00Z" type="user:update" wikiid="your-site-wiki-id" language="en-US" version="5">
<request id="...">...</request>
<user id="42"/>
</event>
- Events identify what changed (a page ID, a user ID), not the changed values. To get the current state, call the API, for example
GET /@api/deki/users/{id}. - Ignore elements and attributes you do not recognize. More may be added.
- Payloads are usually small. Very large events (roughly 256 KB or more) are not delivered. Contact Support if you need exact limits.
Signature verification
Each delivery is signed so you can confirm it came from your Expert site and was not modified:
- Algorithm: HMAC-SHA256, output as lowercase hexadecimal.
- Key: the subscription's authorization value, exactly as stored, encoded as UTF-8. This includes any prefix such as
Bearer. There is no separate signing secret: no secret is generated, returned, or rotated on its own. - Signed string: the
X-Nexus-Timestampvalue, a period (.), and the raw request body, encoded as UTF-8. Verify against the exact body bytes you received, before parsing or reformatting the XML.
// Example verification (Node.js / Express)
const crypto = require('crypto');
// Use the raw body: app.use(express.raw({ type: 'application/xml' }))
function verifySignature(rawBody, headers, authorizationValue) {
const timestamp = headers['x-nexus-timestamp'];
const signature = headers['x-nexus-signature-256'] || '';
// Reject old deliveries to limit replay (5 minutes)
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return false;
}
const expected = crypto
.createHmac('sha256', Buffer.from(authorizationValue, 'utf8'))
.update(`${timestamp}.`)
.update(rawBody)
.digest('hex');
return signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
Rotate the authorization value
Changing the authorization value in the Dashboard or with PUT /@api/deki/webhooks/{id} changes both the Authorization header and the signing key. The change applies to events processed after you save, usually within a few seconds. There is no overlap period, and deliveries that are already being sent use the old value. To rotate without losing events:
- Configure your endpoint to accept both the old and the new value, both for authentication and for signature verification.
- Update the subscription with the new value.
- After a few minutes, stop accepting the old value.
Event types
Each subscription receives only the event types you add to it. For the full list of event types, and which user changes raise User_Create and User_Update, see Supported events.
Delivery behavior
Batching, concurrency, and ordering
- Events are delivered asynchronously. They are collected per site for up to 5 seconds or 60 events, whichever comes first, and then sent. Expect a delay of a few seconds after the triggering action.
- Each event in a batch is sent as its own
POST. Batches are never combined into one request. - Several deliveries to the same subscription can be in flight at the same time. There is no per-subscription concurrency limit.
- Delivery order is not guaranteed, even for events about the same page or user. Use the event's
datetimeattribute, or read the current state from the API, rather than relying on arrival order. - Events raised by bulk operations, such as imports, are delivered like any other event. Expect bursts of traffic.
Timeout
- Each delivery times out after 5 seconds. The timeout covers the whole exchange: connecting, sending the request, and receiving the complete response.
- The timeout is the same for all subscriptions and cannot be configured.
Retries
Each event is delivered to each subscription once. Failed deliveries are not retried, whatever the cause: timeouts, connection errors, and all non-2xx status codes. A failed delivery counts as one failure.
Redirects
Redirects are followed automatically, but we do not recommend relying on them. Following a 301, 302, or 303 redirect changes the request to a GET without a body, and the Authorization header is dropped. Register the final URL instead.
Failure handling
Any response other than 2xx counts as a failed delivery. This includes 4xx responses such as 401, 403, 413, and 429, as well as 5xx responses, timeouts, and connection errors. Retry-After headers are ignored.
- Counting: Failures are counted per site and per webhook URL. Subscriptions on the same site that share a URL share one counter. The count resets if 15 minutes pass with no new failure. A single successful (2xx) delivery resets the count and the failure history.
- Temporary pause: After 3 failures in a row, delivery to that URL pauses for about 15 minutes. Events raised during the pause are dropped, not queued.
- Deletion: If the URL keeps failing and its first failure was 12 or more hours ago, the subscription is deleted. It is not just paused or disabled. A deleted subscription no longer appears in the Dashboard or in
GET /@api/deki/webhooks, and it cannot be re-enabled. Create a new subscription and add its event types again.
Paused subscriptions
While a subscription is paused in the Dashboard (or has active set to false), its events are dropped. They are not delivered when you reactivate it.
Missed deliveries and replay
Missed or failed deliveries cannot be replayed or redelivered. This includes events missed during an outage, during a pause, or before you recreate a deleted subscription. To recover, read the current state from the API, for example GET /@api/deki/pages/{id}, GET /@api/deki/users, or the site's recent changes.
Monitoring
- Delivery logs and delivery history are not available.
- The
last-failure-dateandconsecutive-failuresfields returned byGET /@api/deki/webhooksare not currently updated. Do not use them to check liveness. - To check that a subscription is still active, call
GET /@api/deki/webhooks/{id}regularly. A 404 response or a missing subscription means it was deleted. - Monitor your endpoint's own logs for response codes, response times, and gaps in received events.
Network and allowlisting
Source IP addresses for webhook deliveries are not published. Contact Support if you need to allowlist deliveries by IP address. In the meantime, authenticate deliveries with the Authorization header and signature verification.
Best practices
- Respond quickly: Return a 2xx response well within 5 seconds. Store the event and process it in the background.
HTTP/1.1 200 OK Content-Length: 0
- Return 2xx for anything you have accepted: Every non-2xx response counts toward the pause and deletion of your subscription and is never retried. Avoid returning 4xx for events you want to ignore, and avoid rate limiting deliveries with 429.
- Handle duplicates: Use the event
idattribute to detect duplicates.X-Nexus-Delivery-Idis unique to each delivery and is useful for logging and support requests. - Verify signatures: Check
X-Nexus-Signature-256and reject deliveries with an oldX-Nexus-Timestamp. - Reconcile regularly: Because missed events are not redelivered, periodically compare your data with the API, especially after downtime.
- Check that your subscriptions still exist: Poll
GET /@api/deki/webhooks/{id}and recreate the subscription if it was deleted.
Troubleshoot common issues
Test your integration
- Start small: Begin with a single event type.
- Test signature verification: Confirm that your verification matches the signature on a real delivery.
- Handle edge cases: Test with malformed requests, duplicate events, and out-of-order events.
- Log everything: Record
X-Nexus-Delivery-Id,X-Nexus-Event-Type, and your response code for every delivery.
Webhook not firing
- Check that the subscription still exists. It may have been deleted after repeated failures.
- Check that the subscription is active and that the event type is added to it.
- Check that the URL starts with
https://. - Check that the change you made raises an event. See User events.
- If your endpoint recently failed 3 times in a row, delivery may be paused for up to 15 minutes.
Signature mismatch
- Use the full authorization value as the key, including any
Bearerprefix. - Compute the signature over the raw body bytes, not over parsed or reformatted XML.
- Compare against the lowercase hexadecimal value, without a
sha256=prefix. - If you recently changed the authorization value, accept both the old and the new value for a few minutes.
Authentication failures
- Verify that your endpoint expects the authorization value exactly as stored on the subscription.
- If you use a JWT, check that it has not expired. An expired token causes failures that can lead to deletion of the subscription.
Timeout issues
- Make sure your endpoint returns its complete response within 5 seconds.
- Return 200 OK immediately and do heavy processing in the background.
Support
For technical issues or questions about webhook integration:
- Review your endpoint's logs for response codes and timing.
- Contact NiCE KM Support with your site, the subscription ID, the affected delivery IDs (
X-Nexus-Delivery-Id), timestamps, and any error messages.
