# Guide to Nexthink Webhooks: Features and Best Practices

Nexthink webhooks push data out of Nexthink when something happens on your employees' devices. When an alert fires or an event matches a condition you define, Nexthink sends an HTTP request to a third-party tool, so you can open a ticket in your service desk, post to a chat channel, or start a workflow in your own service without anyone watching the Nexthink console.

This guide covers how Nexthink webhooks are built from credentials and templates, how to authenticate them, where they fall short, and how to receive them reliably.

## What are Nexthink webhooks?

A Nexthink webhook is an outbound connector. It is closer to a configurable HTTP request than to a classic event subscription. You write the request yourself: the method, the path and the JSON body. Nexthink fills placeholders in the body from the alert or event that triggered it, and sends the request whenever the webhook's NQL condition matches.

Two objects make up the request:

* A **connector credential**, created under **Administration > Connector credentials**, holds the instance URL (for example `https://{host}:{port}/`) and the authorization to send with it.
* A **webhook**, created under **Administration > Outbound connectors > Webhooks**, holds the NQL condition, the credential to use, the method, a resource path appended to the credential's URL, and the payload template.

Nexthink does not sign these requests. The only authentication is the `Authorization` header that the credential produces. With the **Bearer token** type, that header is the **Header prefix** you set (usually `Bearer`) followed by the **Token**.

## Nexthink webhook features

| Feature | Details |
| --- | --- |
| Configuration | Administration > Outbound connectors > Webhooks |
| Permissions | Central Administrative privileges and the Integrations module |
| Trigger | An NQL condition matching an alert or event |
| Methods | `POST`, `PUT` or `PATCH`, chosen per webhook |
| Destination | The credential's instance URL plus the webhook's Resource path |
| Payload | Your own JSON template with `{{variable_name}}` placeholders |
| Event types and IDs | None defined by Nexthink |
| Authentication | `Authorization` header from the connector credential |
| Signature and timestamp | None |
| Delivery | Throttled, so it may be delayed |
| Retries, timeout, delivery log | Not documented; no central view of call status |

## The payload

Nexthink has no fixed payload and no list of event types. The body is the JSON you type into the webhook's **Payload** field, and the field only accepts JSON. You add placeholders from the NQL data model, and after you fill in the NQL conditions the page lists the **Allowed placeholders** for that webhook. For example, `{{alert.monitor.name}}` is the monitor's name and `{{alert.alert.trigger_time}}` is when the alert fired. Adding `.as(format = text)` makes values such as remote action outputs readable.

A template for alert notifications might look like this:

```json
{
  "source": "nexthink",
  "type": "alert",
  "monitor": "{{alert.monitor.name}}",
  "triggered_at": "{{alert.alert.trigger_time}}"
}

```

Every field name here is your choice. Nexthink adds no event field, so if your receiver needs to tell one webhook from another, put a field like `type` in each template, or give each webhook its own Resource path.

Two documented behaviours affect how you parse the body. When a placeholder has an empty or invalid value, Nexthink cannot replace it and sends the literal `{{variable_name}}` text. And **Send Test** does not replace placeholders at all, so test requests carry the raw template.

> **See exactly what your template produces.** Point a Nexthink credential at a [Hookdeck Inspect](https://inspect.hookdeck.com) URL and use **Send Test** to see the method, headers and body Nexthink sends, with no account or setup required.

## Setting up Nexthink webhooks

### Create a Bearer token credential

1. Generate a long random token, for example with `openssl rand -hex 32`.
2. Open **Administration > Connector credentials** and click **New credential**. If the option is missing from the menu, Nexthink says to ask its support team to enable it.
3. Give it a name and choose **Hypertext Transfer Protocol Secure (HTTPS)** as the protocol.
4. Set the **URL address** to your receiver's scheme and host, in the `https://{host}:{port}/` format Nexthink asks for. The path goes in the webhook's Resource field instead.
5. Choose **Bearer token** as the authorization type. Set **Header prefix** to `Bearer` and paste your token into **Token**.

Nexthink cannot test a credential when you save it. You find out whether it works when you send a test from a webhook.

### Create the webhook

1. Open **Administration > Outbound connectors > Webhooks** and click **New webhook**.
2. Enter a **Name** and **Description**.
3. Write the **NQL Conditions** that select the alerts or events to send.
4. Select the credential you created.
5. Choose the **Method**: `POST`, `PUT` or `PATCH`.
6. Enter the **Resource**, the path Nexthink appends to the credential's URL. It can include `key=value` parameters and placeholders.
7. Write the JSON **Payload** template.
8. Click **Send Test**, then make sure the **Active** toggle is on.

### Routing through Hookdeck

Create a Hookdeck source with the **Nexthink** type. Split its URL between the two Nexthink objects: the scheme and host go in the credential's **URL address**, and the path goes in the webhook's **Resource**.

The source has one field, **Authorization Header**. Choose the Bearer token credential type in Nexthink and paste the whole `Authorization` value into this field, including `Bearer ` and the space after it, for example `Bearer 3f9a...`. Hookdeck compares the whole value. Basic and OAuth credential types are not covered by this source.

The source pre-selects `POST` as the allowed method but does not lock it. If your webhook uses `PUT` or `PATCH`, allow that method on the source as well.

## Securing Nexthink webhooks

Compare the whole `Authorization` header with the value you expect, in constant time. Accept the methods your webhooks use, and allow an empty body.

```javascript
const crypto = require('crypto');
const express = require('express');

// The whole header value, e.g. "Bearer 3f9a...".
const EXPECTED = process.env.NEXTHINK_AUTHORIZATION;

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.all('/webhooks/nexthink', express.raw({ type: '*/*' }), (req, res) => {
  if (!['POST', 'PUT', 'PATCH'].includes(req.method)) {
    return res.sendStatus(405);
  }

  const received = req.get('authorization');
  if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
    return res.sendStatus(401);
  }

  const raw = Buffer.isBuffer(req.body) ? req.body.toString('utf8') : '';
  let payload = null;
  if (raw.length > 0) {
    try {
      payload = JSON.parse(raw);
    } catch {
      return res.sendStatus(400);
    }
  }

  // Store the payload, then process it outside the request.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hmac
import os

EXPECTED = os.environ.get("NEXTHINK_AUTHORIZATION", "")

def verify_nexthink_request(headers) -> bool:
    if not EXPECTED:
        return False  # never accept when unconfigured
    received = headers.get("authorization")
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))

```

A Bearer token proves the request came from someone who holds the token. It says nothing about the body, and with no signature or timestamp a captured request can be replayed. Keep the receiver on HTTPS, use a long random token, and if it might have leaked, put a new token in the credential and update your receiver at the same time.

> **Make Nexthink webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks the Authorization value on every request, records each delivery so you can see what Nexthink sent, and retries delivery to your service on your schedule.

## Nexthink webhook limitations and pain points

### No delivery log

**The Problem:** When a ticket never appears downstream, you cannot tell whether Nexthink sent the request or what came back.

**Why It Happens:** Nexthink's FAQ says there is currently no central place to see the status of the calls webhooks make. It recommends **Send Test** instead, and a test does not replace placeholders.

**Workarounds:**

* Log every request your receiver gets, with headers and body.
* Use **Send Test** to check the credential, method and resource, knowing the body will contain raw placeholders.

**How Hookdeck Can Help:** Every request to a Hookdeck source is recorded with its headers and body, so you can see each delivery as Nexthink sent it. Hookdeck answers Nexthink with a `200` either way, but a request with a missing or wrong `Authorization` value is recorded as failed verification and never reaches your service. [Issues](/docs/issues) can notify you when deliveries to your service fail.

### Retries are not documented

**The Problem:** If your receiver is down when an alert fires, the request may be lost.

**Why It Happens:** Nexthink's webhook documentation does not describe retries or a timeout.

**Workarounds:**

* Keep the receiver simple and highly available, and return `200` as soon as the request is stored.
* For alerts that matter, compare the alerts shown in Nexthink with what your receiver recorded.

**How Hookdeck Can Help:** Hookdeck accepts each request and stores it before forwarding, then delivers to your service with [retries](/docs/retries) you configure, so your downtime does not drop alerts.

### Removed credentials fail silently

**The Problem:** A webhook stops sending and still looks active.

**Why It Happens:** Nexthink's FAQ says a webhook whose credential has been removed will not process any requests and "will give a false impression that it is working".

**Workarounds:**

* Delete the webhook before the credential it uses, as Nexthink recommends.
* Alert on silence: if a receiver that normally gets traffic goes quiet, check the webhook.

**How Hookdeck Can Help:** Because every delivery is recorded per source, a gap in traffic is visible in the Hookdeck dashboard.

### Placeholders can arrive unfilled

**The Problem:** Your receiver gets `"{{alert.monitor.name}}"` instead of a monitor name.

**Why It Happens:** Nexthink sends a placeholder as literal text when its value is empty or invalid, and always does so on **Send Test**.

**Workarounds:**

* Treat any value that still starts with `{{` as missing.
* Only use placeholders listed under **Allowed placeholders** for the webhook's NQL condition.

**How Hookdeck Can Help:** A [filter](/docs/filters) can hold back requests that still contain unfilled placeholders, and a [transformation](/docs/transformations) can replace them with `null` before your service sees them.

### No event IDs

**The Problem:** You cannot tell a repeated request from a new one.

**Why It Happens:** Nexthink adds no ID or event type. The body only contains what your template puts in it.

**Workarounds:**

* Include identifying placeholders in the template, such as the monitor name and `{{alert.alert.trigger_time}}`, and build an idempotency key from them. Nexthink's own PagerDuty example builds an incident key the same way, as `incident_{{alert.alert.trigger_time}}`.

**How Hookdeck Can Help:** [Deduplication](/docs/deduplication) can drop requests whose body, or chosen fields, match one seen recently.

### Throttled delivery

**The Problem:** Requests arrive later than the alert.

**Why It Happens:** Nexthink regulates how fast it sends to third-party tools so they are not overloaded, and says delivery "is not immediate and some delays may occur".

**Workarounds:**

* Do not use the arrival time as the alert time. Put `{{alert.alert.trigger_time}}` in the template instead.

**How Hookdeck Can Help:** Hookdeck records when each request arrived, and you can set a [max delivery rate](/docs/destinations#set-a-max-delivery-rate) on your side too, so a burst of alerts does not overload your service.

## Best practices

### Store the whole Authorization value

Keep the expected value exactly as Nexthink sends it, prefix and space included, and compare it whole. Do not strip `Bearer ` in code.

### Put a type field and an ID in every template

Since Nexthink defines neither, add your own `type` field to each webhook's payload, and include enough placeholders to identify the alert. That gives your receiver something to route on and to deduplicate with. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Accept the methods you configured

If any webhook uses `PUT` or `PATCH`, make sure your receiver, and the Hookdeck source if you use one, accepts it.

### Acknowledge fast and process asynchronously

Check the header, store the request, return `200`, and do the work afterwards. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Nexthink webhooks are requests you design: you choose the method, the path and the JSON body, and Nexthink fills in placeholders and attaches the credential's `Authorization` header. That flexibility comes with gaps. There is no signature, no event ID, no documented retry policy and no delivery log. Check the whole header in constant time over HTTPS, put your own type and identifiers in each template, and treat unfilled placeholders as missing data.

[Hookdeck Event Gateway](https://hookdeck.com) checks the header, records every request, and retries delivery to your service, which gives you the delivery history Nexthink does not.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Nexthink webhooks reliably in minutes.