# Guide to New Relic Webhooks: Features and Best Practices

New Relic webhooks carry your alerts out of the observability platform. When an alert condition opens an issue, or the issue is acknowledged or closed, a New Relic workflow can POST a JSON notification to your own service, so you can open tickets, page people through a tool New Relic does not integrate with, run automated remediation, or record incidents in your own systems.

This guide covers how Webhook destinations and workflows fit together, how to authenticate notifications with HTTP Basic credentials, how to shape the payload with Handlebars, and the practices that make a New Relic receiver reliable.

## What are New Relic webhooks?

In New Relic Alerts, a **workflow** decides which issues to notify about and where. A **destination** is where notifications go. A **Webhook** destination is a URL that receives an HTTP `POST` with a JSON body whenever a workflow that uses it fires.

The body is not fixed. New Relic starts you from a default JSON template, and you can rewrite it with Handlebars variables such as `issueId`, `issueTitle`, `issuePageUrl`, `priority` and `state`. Whatever your template renders is what your endpoint receives, so there is no New Relic event type to route on unless your template includes one.

New Relic does not sign webhook requests. The destination can carry an `Authorization` header, with either **Basic Authentication** or a **Bearer Token**, and you can add custom headers whose values New Relic encrypts and masks.

## New Relic webhook features

| Feature | Details |
| --- | --- |
| Configuration | **Alerts** > **Destinations** > **Webhook**, then a workflow that notifies it |
| Method and format | `POST`, JSON body |
| Payload | A default JSON template, editable with Handlebars variables. It must render to valid JSON to be saved |
| Authorization | Optional: Basic Authentication or Bearer Token |
| Custom headers | Encrypted and masked from users and internal logs. A destination header overrides the same key on the channel |
| Secure URL | Path and query secrets encrypted after the domain |
| Signature and timestamp | None |
| Event type | None. Defined by your template |
| Testing | **Send test notification** from the workflow's template editor |
| Two-way sync | Acknowledge or close issues through NerdGraph |
| Timeout, retries, source IPs | Not documented |

## Payloads

Because the payload is a template, the useful question is what to put in it. New Relic's own templates for other tools show the variables available. Its xMatters template, for example, builds an `incident_id` from `issueId`, an `incident_url` from `issuePageUrl`, an `incident_acknowledge_url` from `issueAckUrl`, and derives a `current_state` of `closed`, `acknowledged` or `open` from whether `issueClosedAt` or `issueAcknowledgedAt` is set.

A small template for your own receiver might be:

```handlebars
{
  "issue_id": {{json issueId}},
  "title": {{json issueTitle}},
  "priority": {{json priority}},
  "state": {{json state}},
  "url": {{json issuePageUrl}},
  "closed_at": {{#if issueClosedAtUtc}} {{json issueClosedAtUtc}} {{else}} null {{/if}}
}

```

Use `{{json ...}}` for strings so quotes in titles do not break the JSON, and wrap optional variables in `{{#if}}`: New Relic notes that "undefined type" errors usually mean an attribute does not exist on the issue. The **Preview** pane shows the rendered payload, and the editor will not save a template that does not produce valid JSON.

> **Inspect New Relic webhooks as they arrive.** Point a Webhook destination at a [Hookdeck Inspect](https://inspect.hookdeck.com) URL and send a test notification to see exactly what your template renders, with no account or setup required.

## Setting up New Relic webhooks

Choose a username and a long random password first, for example with `openssl rand -hex 32` for the password.

1. Go to **one.newrelic.com** > **All capabilities** > **Alerts**, click **Destinations**, and select **Webhook**.
2. Enter a **Webhook name** and the **Endpoint URL**.
3. Turn on **Use authorization**, choose **Basic Authentication**, and enter the username and password.
4. Save the destination.
5. Open **Alerts** > **Workflows**, edit a workflow or add one, and choose your Webhook destination.
6. Edit the payload template, check the **Preview**, and click **Send test notification**.

To route through Hookdeck, create a Hookdeck source with the **New Relic** type and use its URL as the endpoint URL. The source asks for a **Username** and **Password**: enter the same pair. It accepts `POST`.

The Hookdeck source checks HTTP Basic credentials only. If you choose **Bearer Token**, or authenticate with a custom header, the New Relic source type will not verify the request. Hookdeck also never answers with a `401` challenge, so the credentials have to be sent with the first request. New Relic's docs do not describe the exact header it sends, so confirm on your first test notification that it arrives with an `Authorization: Basic` header.

## Securing New Relic webhooks

With Basic Authentication, every notification carries `Authorization: Basic <base64(username:password)>`. Decode it, split on the first colon, and compare the username and password with your stored values in constant time.

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

const USERNAME = process.env.NEW_RELIC_WEBHOOK_USERNAME;
const PASSWORD = process.env.NEW_RELIC_WEBHOOK_PASSWORD;

function safeEqual(a, b) {
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

function verifyBasicAuth(header) {
  if (!USERNAME || !PASSWORD || typeof header !== 'string') return false;
  const match = /^basic\s+([A-Za-z0-9+/]+={0,2})\s*$/i.exec(header);
  if (!match) return false;
  const decoded = Buffer.from(match[1], 'base64');
  const colon = decoded.indexOf(0x3a); // split on the first colon
  if (colon === -1) return false;
  const userOk = safeEqual(decoded.subarray(0, colon), Buffer.from(USERNAME, 'utf8'));
  const passOk = safeEqual(decoded.subarray(colon + 1), Buffer.from(PASSWORD, 'utf8'));
  return userOk && passOk;
}

const app = express();

app.post('/webhooks/new-relic', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifyBasicAuth(req.get('authorization'))) {
    return res.sendStatus(401);
  }

  let notification;
  try {
    notification = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Fields are whatever your template renders, e.g. notification.issue_id and notification.state.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import base64
import binascii
import hmac
import os

USERNAME = os.environ.get("NEW_RELIC_WEBHOOK_USERNAME", "")
PASSWORD = os.environ.get("NEW_RELIC_WEBHOOK_PASSWORD", "")

def verify_basic_auth(header) -> bool:
    if not USERNAME or not PASSWORD or not header:
        return False
    scheme, _, encoded = header.partition(" ")
    if scheme.lower() != "basic":
        return False
    try:
        decoded = base64.b64decode(encoded.strip(), validate=True)
    except (binascii.Error, ValueError):
        return False
    user, sep, password = decoded.partition(b":")
    if not sep:
        return False
    user_ok = hmac.compare_digest(user, USERNAME.encode("utf-8"))
    pass_ok = hmac.compare_digest(password, PASSWORD.encode("utf-8"))
    return user_ok and pass_ok

```

Basic credentials are static. They authenticate the sender, not the body, so a modified payload still passes, and with no timestamp a captured request can be replayed. Serve the endpoint over HTTPS only, use a long random password, and change it on the destination and in your environment together if it might have leaked.

> **Make New Relic webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks the Basic credentials you configure, queues every alert notification durably, and retries delivery to your service on a schedule you control.

## New Relic webhook limitations and pain points

### Only one of three auth options fits the source type

**The Problem:** A destination set up with a Bearer Token or an encrypted custom header never verifies in Hookdeck's New Relic source.

**Why It Happens:** New Relic offers Basic Authentication, Bearer Token and custom headers. Hookdeck's New Relic source type checks HTTP Basic credentials only.

**Workarounds:**

* Use **Basic Authentication** on the destination.
* If you are tied to a bearer token or custom header, check it in your own receiver instead.

**How Hookdeck Can Help:** With Basic Authentication, the New Relic source compares the username and password you enter. Hookdeck answers New Relic with a `200` either way, but requests without matching credentials are recorded as failed verification and never reach your service.

### No documented delivery guarantees

**The Problem:** You cannot tell from the docs how long New Relic waits for your endpoint, whether it retries, or what happens to a notification when your service is down.

**Why It Happens:** The Webhook destination documentation covers configuration and payloads, not delivery behaviour.

**Workarounds:**

* Respond quickly and process afterwards, so a slow handler never becomes a lost alert.
* Reconcile open issues against New Relic through NerdGraph, so a missed notification is noticed.

**How Hookdeck Can Help:** Hookdeck accepts each notification and stores it, then delivers to your service with [retries](/docs/retries) you configure. You can replay any notification after a fix, and [issues](/docs/issues) alert you when deliveries to your service fail.

### The template is the contract

**The Problem:** Someone edits a workflow's template and your receiver starts failing, or a variable that does not exist on some issues renders as an error.

**Why It Happens:** The payload is whatever the template renders, per workflow. Different workflows can send different shapes to the same endpoint.

**Workarounds:**

* Keep one template for every workflow that targets the same endpoint, and treat changes to it like an API change.
* Guard optional variables with `{{#if}}` and give them defaults.
* Include a constant such as `"source": "new-relic"` and a schema version in the template.

**How Hookdeck Can Help:** Hookdeck keeps every request with its full body, so you can see exactly what a changed template sent and replay it against your fixed handler. [Transformations](/docs/transformations) can normalise differing shapes before they reach your service.

### Several notifications per issue

**The Problem:** One issue can produce several notifications as it opens, is acknowledged and closes, and your receiver has to reconcile them.

**Why It Happens:** A workflow can notify on more than one kind of issue update, and nothing in the delivery itself orders them unless your template includes timestamps.

**Workarounds:**

* Include `issueId` and the relevant timestamps in your template.
* Upsert by issue ID and only move state forward.

**How Hookdeck Can Help:** Hookdeck [deduplication](/docs/deduplication) can drop repeated notifications based on fields you choose, such as the issue ID and state.

## Best practices

### Design the template for your receiver

Include an issue identifier, the state, priority, a link back to New Relic and the timestamps you care about. Use `{{json}}` for every string so the output stays valid JSON.

### Make handlers idempotent

Key on the issue ID plus its state or the relevant timestamp, and store that key before you act. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Close the loop with NerdGraph

If your receiver handles the incident, acknowledge or close the issue back in New Relic through NerdGraph, so both systems agree on its state.

### Acknowledge fast and process asynchronously

Remediation or ticket creation can take a while. Verify the credentials, store the notification, return `200`, and do the work afterwards. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

New Relic sends alert notifications to Webhook destinations as an HTTP `POST` whose JSON body is your own Handlebars template, with no signature and no fixed event type. Use Basic Authentication on the destination, compare the decoded username and password in constant time over HTTPS, put an issue ID and state in your template, and build the handler to cope with duplicates and undocumented delivery behaviour.

[Hookdeck Event Gateway](https://hookdeck.com) checks the Basic credentials, keeps every notification, and retries and replays delivery to your service, so an outage on your side does not mean a missed alert.

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