Guide to Honeybadger Webhooks: Features and Best Practices
Honeybadger webhooks tell your systems when something changes in a monitored project: an error occurs or comes back after being resolved, an uptime check goes down, a scheduled job misses its check-in, or a deploy is recorded. If you use Honeybadger for error and uptime monitoring, webhooks are how those alerts reach your own incident tooling, ticket queues and dashboards.
This guide covers how Honeybadger's Webhook integration is set up and authenticated, the 13 events and their payloads, what Honeybadger leaves undocumented, and the best practices for running a receiver in production.
What are Honeybadger webhooks?
Each Honeybadger project can have a Webhook integration that posts a JSON payload to a URL you choose. Every payload has an event field naming what happened and a human-readable message, plus objects that depend on the event.
Honeybadger does not sign webhook payloads. Instead, every outbound request from Honeybadger's servers (webhooks, source map fetches and uptime checks) carries a Honeybadger-Token header: a secret token derived from the project's API key. Your endpoint authenticates the request by comparing that header with the token it has stored.
Honeybadger webhook features
| Feature | Details |
|---|---|
| Configuration | Project Settings > Alerts & Integrations > Webhook, for users with admin rights on the project |
| Authentication | Honeybadger-Token: <token>, sent raw with no prefix |
| Where the token lives | The API Key tab of the project settings |
| Token rotation | The token changes when the project API key is reset |
| Optional header | Authorization: Bearer <value>, if you fill in the integration's Bearer token field |
| Body signature | None |
| Routing | The body's event field |
| Events | 13 |
| Event ID | None documented |
| Retries and timeout | Not documented |
| IP allowlist | Not available. Honeybadger uses a dynamic range for outbound requests |
Common events
| Event | Sent when | Top-level properties |
|---|---|---|
occurred | An error occurs | event, message, project, fault, notice |
resolved | An error is marked as resolved | event, message, project, fault |
unresolved | A resolved error occurs again | event, message, project, fault |
assigned | An error is assigned to a user | event, message, actor, fault, assignee |
commented | A comment is added to an error | event, message, actor, fault, comment |
rate_exceeded | An error rate threshold is exceeded | event, message, project, fault |
volume_spike | A project's error volume spikes above its learned baseline | event, message, project, volume_spike |
deployed | A deployment is recorded | event, message, project, deploy |
down | An uptime check fails | event, message, project, site, outage |
up | An uptime check succeeds after being down | event, message, project, site, outage |
cert_will_expire | An SSL certificate is about to expire | event, message, project, site |
check_in_missing | An expected check-in is missing | event, message, project, check_in |
check_in_reporting | A check-in reports successfully | event, message, project, check_in |
Here is Honeybadger's check_in_missing example, trimmed:
{
"event": "check_in_missing",
"message": "[Voyager Test] MISSING: Voyager hasn't checked in for 4 years",
"project": {
"id": 67747,
"name": "Voyager Test",
"token": "abcd1234",
"environments": [{ "id": 94721, "name": "development", "notifications": true }],
"unresolved_fault_count": 3,
"fault_count": 6
},
"check_in": {
"id": "XYZLOL",
"name": "Voyager",
"state": "missing",
"schedule_type": "simple",
"reported_at": "2019-12-23T21:39:37.124397Z",
"expected_at": "2023-10-30T19:42:28.853152Z",
"missed_count": 33385,
"report_period": "1 hour",
"details_url": "https://app.honeybadger.io/projects/67747/check_ins"
}
}
The project object is large in practice. It includes the project's environments, owner, users and counts, as well as a token field, so treat stored payloads as sensitive. The fault object on error events carries id, klass, environment, resolved, notices_count and last_notice_at, and down and up events include an outage with down_at and up_at.
Setting up Honeybadger webhooks
- Open the project in Honeybadger, go to Project Settings, and open the API Key tab. Copy the token shown there and store it server-side, for example as
HONEYBADGER_TOKEN. - Go to the Alerts & Integrations tab and choose the Webhook integration, below the list of personal alert integrations.
- Enter your URL, including the protocol, for example
https://example.com/webhooks/honeybadger. - Leave Bearer token blank unless something in front of your endpoint rejects requests without an
Authorizationheader. - Save.
If you receive Honeybadger webhooks through Hookdeck, enter the token from the API Key tab in the Hookdeck source. Hookdeck checks the Honeybadger-Token header, not the optional Authorization bearer token.
Honeybadger cannot reach localhost. For local development, hookdeck listen 3000 honeybadger --path /webhooks/honeybadger with the Hookdeck CLI gives you a public URL that forwards to your machine.
Securing Honeybadger webhooks
Read Honeybadger-Token, compare it with your stored token in constant time, and reject the request if it is missing or different. The header holds the raw token, so there is nothing to strip. Fail closed when the token is not configured.
const crypto = require('crypto');
const express = require('express');
function verifyHoneybadgerToken(headerValue, token) {
if (typeof headerValue !== 'string' || !token) return false;
const received = Buffer.from(headerValue, 'utf8');
const expected = Buffer.from(token, 'utf8');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
const app = express();
app.post('/webhooks/honeybadger', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyHoneybadgerToken(req.get('honeybadger-token'), process.env.HONEYBADGER_TOKEN)) {
return res.sendStatus(401);
}
const payload = JSON.parse(req.body.toString('utf8'));
// Route on payload.event, then hand off to a queue.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
def verify_honeybadger_token(header_value, token) -> bool:
if not header_value or not token:
return False
return hmac.compare_digest(header_value.encode("utf-8"), token.encode("utf-8"))
# verify_honeybadger_token(request.headers.get("honeybadger-token"), os.environ["HONEYBADGER_TOKEN"])
If one endpoint serves several Honeybadger projects, keep a token per project, because each project's token is different.
Make Honeybadger webhooks production-ready. Hookdeck Event Gateway checks the
Honeybadger-Tokenheader on every request, keeps each error, uptime and check-in event in a durable queue, and lets you replay any of them into your incident tooling.
Honeybadger webhook limitations and pain points
Resetting the API key breaks every webhook
The Problem: All webhooks start failing authentication at once, and nothing changed in your code.
Why It Happens: Honeybadger-Token is derived from the project API key. Reset the key and the token changes, but your endpoint still holds the old one.
Workarounds:
- Make "update the webhook token" part of your API key rotation runbook.
- Alert on a run of authentication failures from Honeybadger, since a whole project failing together usually means a rotated key.
How Hookdeck Can Help: With a Hookdeck source, the token lives in one place. Update it there and every downstream service keeps working, and the requests rejected with VERIFICATION_FAILED in the meantime are visible in Hookdeck's request log.
A static token instead of a signature
The Problem: Anyone who obtains the token can send a payload that passes the check, and nothing protects the body from being changed in transit.
Why It Happens: Honeybadger sends the same token on every request rather than signing each body. The same token also goes out with source map fetches and uptime checks.
Workarounds:
- Use an HTTPS endpoint and never log the token header.
- Before acting on high-impact events, such as auto-resolving tickets, confirm the fault through the Honeybadger API.
How Hookdeck Can Help: Hookdeck checks the header before anything is queued for your service, so forged requests never reach your handlers, and you can keep the token out of your application entirely by verifying Hookdeck's own signature at your destination instead.
No documented retries
The Problem: If your endpoint is down when a down or check_in_missing alert fires, you do not know whether Honeybadger will try again.
Why It Happens: Honeybadger's webhook documentation states the method and body but no timeout, success criterion or retry policy.
Workarounds:
- Keep the endpoint fast and highly available, and return
200as soon as the token is checked. - For uptime and check-in alerts, keep Honeybadger's built-in email or chat alerts switched on as a second channel.
How Hookdeck Can Help: Hookdeck accepts each request as soon as it arrives and stores it, then delivers it to your service with automatic retries, so a deploy or outage on your side does not lose an alert.
No event ID for deduplication
The Problem: If the same alert arrives twice, your handler can open two tickets.
Why It Happens: Payloads carry event and the related objects, but no unique delivery or event identifier.
Workarounds:
- Build a key from
eventand the object's own identity, such asfault.idwithfault.notices_count, orsite.idwithoutage.down_at. - Make ticket creation an upsert keyed on the fault or site.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated requests based on the fields you choose.
Best practices
Check Honeybadger-Token, not the bearer token
The Honeybadger-Token header is sent on every request. The bearer token is an optional extra for services that insist on an Authorization header. Verify the one Honeybadger always sends.
Route on event and accept new ones
Switch on the body's event. Log and acknowledge any event you do not recognise rather than returning an error, so a new event type never shows up as a failed delivery.
Acknowledge fast and process asynchronously
Return 200 once the token matches and the payload is stored, then create tickets or page people in the background. See why to process webhooks asynchronously.
Pair down with up, and check_in_missing with check_in_reporting
Use the matching recovery event to close what the failure event opened. Key incidents on site.id or check_in.id so the pair always finds each other. See our guide to webhook idempotency.
Conclusion
Honeybadger webhooks are unsigned JSON posts with an event field, authenticated by the Honeybadger-Token header that comes from the project's API key. Compare that header in constant time, update it whenever the API key is reset, route on event, and build your own deduplication key, because Honeybadger documents neither an event ID nor a retry policy.
Hookdeck Event Gateway checks the Honeybadger-Token header, stores every alert, and retries delivery to your service, so an outage on your side does not drop an error or downtime notification.
Get started with Hookdeck for free and handle Honeybadger webhooks reliably in minutes.