> ## Documentation Index
> Fetch the complete documentation index at: https://help.neurapulse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Notify your own systems the moment a test taker completes an assessment, with signed, verifiable deliveries.

A **webhook** lets Neurapulse notify your own system when something happens in your workspace, instead of your system having to ask. You register a URL you control and choose which events it should receive; Neurapulse then sends an HTTPS `POST` request to that URL whenever one of those events occurs. The first available event is a test taker completing an assessment, with more event types to come.

<Info>
  Webhook payloads carry identifiers and status information only — never scores or percentiles, for any event type.
</Info>

## Managing Your Webhooks

Webhooks are managed per workspace, under **Workspace Settings → Webhooks**. Each workspace can register up to **5 URLs**.

<Steps>
  <Step title="Add a webhook">
    Choose **Add new webhook**, select the events you want delivered, and enter your endpoint URL. The URL must use `https://` and point to a publicly reachable address.
  </Step>

  <Step title="Save your signing secret">
    When the webhook is created, Neurapulse shows its **signing secret** (`whsec_...`) exactly once. Copy it and store it securely in your receiving system — it cannot be viewed again. If it is lost, delete the webhook and add it again to get a new secret.
  </Step>

  <Step title="Test the URL">
    From the webhook's actions menu (**⋮**), choose **Test URL**. Neurapulse sends a signed `webhook.test` request to your endpoint and shows the response inline, so you can confirm connectivity before real events flow.
  </Step>
</Steps>

From the same actions menu you can also **edit** which events a URL receives, **enable or disable** it, and **delete** it. Deleting a webhook removes all of its event subscriptions and stops deliveries immediately.

## What a Delivery Looks Like

Every delivery is an HTTPS `POST` with a JSON body and three headers:

| Header                    | Purpose                                                                                                                                                            |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `X-Neurapulse-Event`      | The event type, e.g. `test_result.completed`                                                                                                                       |
| `X-Neurapulse-Timestamp`  | Unix timestamp of the delivery, used in the signature                                                                                                              |
| `X-Neurapulse-Signature`  | `sha256=<hex>` — the HMAC your system verifies                                                                                                                     |
| `X-Neurapulse-Webhook-Id` | Identifier of the webhook registration the delivery belongs to — useful when one endpoint receives deliveries from several registrations, each with its own secret |

An example `test_result.completed` payload:

```json theme={null}
{
  "event": "test_result.completed",
  "occurredAt": "2026-07-31T17:42:11.512Z",
  "projectId": "3f0e6f0a-6f6e-4f6e-9f0a-2b8c41d90e17",
  "projectEntryId": 1042,
  "testResultId": 2211,
  "referenceId": "DRIVER-4821",
  "completionStatus": "Completed",
  "completedAt": "2026-07-31T17:42:05Z"
}
```

* `projectId` is the id of the project the test belongs to, as shown in the portal — use it to route deliveries when several projects feed the same endpoint.
* `completionStatus` is the test's status — most commonly `Completed` or `HighRisk`. Treat it as an open set and don't fail on unfamiliar values; a value of `HighRisk` is the signal that the result needs attention.
* `referenceId` is your own identifier for the test taker, if one was supplied (see [Reference IDs](/docs/Integrations/reference-ids)); otherwise `null`.

Your endpoint should respond with a `2xx` status code promptly. Any other response, or no response within 10 seconds, counts as a failed delivery.

## Verifying the Signature

Always verify deliveries before trusting them — anyone who discovers your endpoint URL could send it fake requests. The signature proves a request came from Neurapulse and was not altered.

Compute an HMAC-SHA256 of `"{timestamp}.{raw request body}"` using your signing secret, and compare it (hex-encoded, prefixed with `sha256=`) to the `X-Neurapulse-Signature` header:

```javascript theme={null}
const crypto = require("crypto");

function verify(secret, timestampHeader, rawBody, signatureHeader) {
  const expected =
    "sha256=" +
    crypto
      .createHmac("sha256", secret)
      .update(`${timestampHeader}.${rawBody}`)
      .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader),
  );
}
```

<Tip>
  Reject requests whose timestamp is more than a few minutes old. Because the timestamp is part of the signed material, this prevents captured requests from being replayed later.
</Tip>

Each URL has its own signing secret, and all events delivered to that URL are signed with the same one — your receiver only ever needs one secret per endpoint.

## Retries and Automatic Disabling

Deliveries are retried automatically, so a brief outage on your side does not lose events:

* A failed delivery is retried with increasing delays — roughly 1 minute, 5 minutes, 30 minutes, 2 hours, then 12 hours.
* After **5 consecutive failures**, the delivery is abandoned and the webhook URL is **automatically disabled** to stop sending into a dead endpoint.
* A disabled webhook stays visible in Workspace Settings with a **Disabled** status. Once your endpoint is healthy again, re-enable it from the actions menu — deliveries resume for new events.

<Warning>
  Deliveries can occasionally arrive more than once for the same event. Use `testResultId` to deduplicate if your system is sensitive to repeats.
</Warning>

## Requirements and Limits

* URLs must use `https://` and resolve to a public address — internal or private-network addresses are rejected.
* Redirects are not followed; the registered URL must answer directly.
* Up to 5 webhook URLs per workspace; the same URL can subscribe to multiple event types as they become available.
