Skip to content

Webhooks allow you to receive HTTP notifications of events in the Nexus Support-a-Creator system. Nexus sends each event to an endpoint you expose as an HTTP POST request, containing a JSON payload describing the event.

{
"eventId": "1_63OfIMngN5aqHEUixc_",
"createdAt": "2023-02-08 22:56:42.055676",
"eventType": "Bounty",
"eventAction": "BountyReward",
"event": {
"attributionGroupId": "B8b84wld-ohveWtgQPJsS",
"attributionGroupName": "Great Game",
"creatorId": "9jd7RBR_B2BZ0uWO3_lgv",
"bountyId": "tzPlV9PeX9Tsxg158pZ47",
"bountyName": "Refer 5 Players",
"bountyProgressId": "4a3C8DLDjPToZ_BMRrYXx",
"bountyRewards": [
{
"id": "prYjeQugJG1n8qFCjW2rM",
"name": "In Game Title",
"amount": 1,
"externalId": "dalfjkAsdlgJkdal",
"creatorRewardId": "PDKczbm3bmNFZWcrelC5N",
"userId": "55d57cffaa074cc6b8d332f5d77661f2"
}
]
}
}

All notifications will contain eventId, createdAt, eventType, and eventAction. The event field contains the event’s data and will vary based upon the eventAction and eventType combination.

Your response to a webhook request must return a 200 OK status code. If processing of the event will take more than a second or two, consider logging the event and processing it after responding with 200 OK.

Nexus signs notifications using the Standard Webhooks HMAC-SHA256 scheme. Verify the signature before parsing or processing the request body.

HeaderValue
webhook-idEvent ID, also present as eventId in the body. Stable across retries.
webhook-timestampDelivery attempt time, as Unix seconds. Refreshed on each retry.
webhook-signatureA space-separated list of signatures. Each HMAC signature has the form v1,<base64 digest>.

The signed bytes are the UTF-8 encoding of webhook-id, a period, webhook-timestamp, another period, and the raw request body. This covers all body fields, including eventType and eventAction. Do not parse and re-serialize the body before verification.

The v1 prefix identifies the Standard Webhooks HMAC scheme. It does not refer to the legacy Nexus signature.

Copy the secret for your endpoint from your program’s Webhooks page. Webshop integrations use their separately configured webhook secret. Keep secrets on your server, outside source control and logs.

For webhook secrets starting with whsec_, remove that prefix and base64-decode the remainder before using it as the HMAC-SHA256 key. The prefix is not used to generate the webhook-signature value. Standard Webhooks libraries handle this decoding for you; pass them the full secret.

Any secret without the whsec_ prefix uses the entire value as UTF-8 key bytes. These rules apply to both program and webshop webhooks.

This helper checks the delivery time, requires a valid standard signature, and compares digests in constant time. It accepts multiple trusted keys so you can prepare your receiver for a coordinated secret rotation.

webhook-verification.mjs
import { createHmac, timingSafeEqual } from 'node:crypto';
export function webhookSigningKey(secret) {
if (typeof secret !== 'string' || !secret) {
throw new Error('Configure a webhook secret');
}
if (!secret.startsWith('whsec_')) return Buffer.from(secret, 'utf8');
const encoded = secret.slice(6);
const key = Buffer.from(encoded, 'base64');
if (key.length < 24 || key.length > 64 || key.toString('base64') !== encoded) {
throw new Error('Invalid webhook secret encoding');
}
return key;
}
export function verifyWebhook(rawBody, headers, trustedKeys, now = Math.floor(Date.now() / 1000)) {
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signatures = headers['webhook-signature'];
if (!Buffer.isBuffer(rawBody) ||
typeof id !== 'string' || !/^[A-Za-z0-9_-]{1,255}$/.test(id) ||
typeof timestamp !== 'string' || !/^\d+$/.test(timestamp) ||
typeof signatures !== 'string' || signatures.length > 4096) return false;
const sentAt = Number(timestamp);
// Reject attempts more than five minutes old or five minutes in the future.
if (!Number.isSafeInteger(sentAt) || Math.abs(now - sentAt) > 300) return false;
return trustedKeys.some((key) => {
const expected = createHmac('sha256', key)
.update(`${id}.${timestamp}.`, 'utf8')
.update(rawBody)
.digest();
return signatures.split(' ').some((entry) => {
const match = /^v1,([A-Za-z0-9+/]{43}=)$/.exec(entry);
if (!match) return false;
const actual = Buffer.from(match[1], 'base64');
return actual.length === expected.length &&
actual.toString('base64') === match[1] &&
timingSafeEqual(actual, expected);
});
});
}

With Express, register express.raw({ type: 'application/json' }) for the webhook route before any express.json() middleware. Decode your configured secrets at startup. Pass the raw body Buffer, request headers, and trusted key array to verifyWebhook. Return 403 Forbidden if verification fails, without trying the legacy signature. After verification, parse the JSON and validate the expected event type, action, and required fields before applying changes.

Keep your server clock synchronized. A fresh retry has a new webhook-timestamp even when the body’s createdAt is much older.

A valid signature does not make a notification single-use. An event can arrive more than once through retries or replay within the timestamp tolerance.

Use webhook-id as the event’s idempotency key within the configured endpoint. Store it durably and enforce uniqueness. Record the ID and apply your business changes in the same transaction, or use a durable inbox that safely resumes interrupted processing. An in-memory set or a separate check-then-insert does not protect multiple server instances or concurrent deliveries.

Return 200 OK for an event you already completed. If processing fails, keep it retryable instead of permanently marking it complete. Retain event IDs for your business replay horizon, including manual redeliveries, rather than expiring them after the five-minute signature window.

During migration, notifications also include the existing x-webhook-signature-256 header. Its signing input and hexadecimal digest remain unchanged. The JSON body retains its existing fields.

  1. Deploy a receiver that requires webhook-signature and verifies the full raw body and delivery headers.
  2. Confirm successful delivery and duplicate handling with Nexus.
  3. Remove legacy verification. Do not accept the legacy signature when the standard signature is missing or invalid.

Nexus will coordinate retirement of the legacy header with existing integrations.

Coordinate rotation with Nexus. Provision the replacement secret to your receiver first and temporarily trust both keys. Once Nexus switches the sender to the replacement, confirm successful verification with that key. Allow in-flight deliveries to finish, then remove the old key from your receiver.

If the old secret is compromised, revoke trust in it immediately. Do not keep a compromised key trusted for a routine overlap window. Keep all key material and signature values out of logs.