Guide to Trigger.dev Webhooks: Features and Best Practices
Trigger.dev webhooks tell you when something in a background jobs project needs attention: a run has failed for good, a deployment failed or succeeded, or an error group is new, has regressed, or has broken through an ignore rule. If you run tasks on Trigger.dev, alert webhooks are how those signals reach your incident tooling, chat bots, status pages and ticket queues.
This guide covers how Trigger.dev alert webhooks are set up and signed, the four alert types and their payloads, the behaviour of error group alerts that catches teams out, and the best practices for running a receiver in production.
What are Trigger.dev webhooks?
Trigger.dev's outbound webhooks are alert notifications. They are not a stream of run lifecycle events: there is no webhook for a run starting or completing successfully. You add a webhook as an alert channel, and Trigger.dev sends a signed JSON POST to your URL whenever a matching alert fires.
Each request carries an x-trigger-signature-hmacsha256 header holding the lowercase hex HMAC-SHA256 of the raw body, keyed with the webhook secret shown on the Alerts page. Trigger.dev's docs show only the SDK helper, webhooks.constructEvent(request, secret), for verifying it. The header name, algorithm and encoding come from that function's source in the Trigger.dev SDK.
Trigger.dev webhook features
| Feature | Details |
|---|---|
| Configuration | Alerts page for run and deployment alerts. Errors page (Configure alerts...) for error group alerts |
| Alert types | alert.run.failed, alert.deployment.success, alert.deployment.failed, alert.error |
| Signature header | x-trigger-signature-hmacsha256 |
| Scheme | HMAC-SHA256, lowercase hex, no prefix |
| Signed content | The raw request body |
| Secret | Shown on the Alerts page when you create the webhook alert, used as a UTF-8 string |
| Replay window | None. No timestamp is signed |
| Envelope | id, created, webhookVersion, type, object |
| SDK helper | webhooks.constructEvent() in @trigger.dev/sdk |
| Retries and timeout | Not documented |
| IP allowlist | None published |
Common events
| Type | Sent when | Main object fields |
|---|---|---|
alert.run.failed | A run fails once its retries are exhausted | task (id, filePath, exportName, version), run (id, number, status, error, isOutOfMemoryError, machine, tags, dashboardUrl) |
alert.deployment.success | A deployment succeeds | deployment (id, status, version, shortCode, deployedAt), tasks, optional git and vercel |
alert.deployment.failed | A deployment fails | deployment (with failedAt), error (name, message, optional stack and stderr), optional git and vercel |
alert.error | An error group alert fires | classification (new_issue, regression or unignored), error (fingerprint, type, message, stackTrace, firstSeen, lastSeen, occurrenceCount, taskIdentifier) |
Every alert also includes object.environment, object.organization and object.project. On run and deployment alerts, environment.type is PRODUCTION, STAGING or PREVIEW, which lets you send production failures to on-call and preview failures somewhere quieter. Error group alerts carry the environment's id and name instead. On deployment alerts, git carries the branch, short commit SHA, commit message and URLs, plus pull request details when there is one.
Trigger.dev documents these fields in tables and publishes no example JSON body, so the shape below is assembled from those tables rather than copied from a delivery:
{
"id": "<unique webhook event ID>",
"created": "<datetime>",
"webhookVersion": "<payload format version>",
"type": "alert.run.failed",
"object": {
"task": { "id": "...", "filePath": "...", "exportName": "...", "version": "..." },
"run": { "id": "...", "number": 42, "status": "...", "error": {}, "dashboardUrl": "..." },
"environment": { "id": "...", "type": "PRODUCTION", "slug": "..." },
"organization": { "id": "...", "slug": "...", "name": "..." },
"project": { "id": "...", "ref": "...", "slug": "...", "name": "..." }
}
}
The value of webhookVersion is not documented either. Log it from your first delivery so you notice if it changes.
Inspect Trigger.dev webhooks as they arrive. Point a webhook alert at a Hookdeck Inspect URL to inspect and replay real deliveries, with no account or setup required.
Setting up Trigger.dev webhooks
- Open your project in the Trigger.dev dashboard and click Alerts in the left-hand menu.
- Click New alert, choose the webhook method, and enter your endpoint URL.
- Choose which alerts it receives: a run fails, a deployment fails, or a deployment succeeds.
- Copy the webhook secret shown on the Alerts page and store it server-side, for example as
TRIGGER_.DEV_ WEBHOOK_ SECRET
Error group alerts are created separately, from the Configure alerts... button on the Errors page. Once created, they appear in the Alerts table labelled "Error group". To disable or delete an alert, use the menu at the end of its row.
If you receive Trigger.dev alerts through Hookdeck, paste the webhook secret into the Hookdeck source.
Securing Trigger.dev webhooks
If your receiver runs on a runtime with Web Request objects, such as Next.js or Remix, the SDK does the work:
import { webhooks, WebhookError } from "@trigger.dev/sdk";
export async function POST(request: Request) {
try {
const event = await webhooks.constructEvent(request, process.env.TRIGGER_DEV_WEBHOOK_SECRET!);
// switch (event.type) { case "alert.run.failed": ... }
return Response.json({ received: true });
} catch (err) {
if (err instanceof WebhookError) {
return Response.json({ error: err.message }, { status: 400 });
}
throw err;
}
}
Elsewhere, verify manually. Compute the hex HMAC-SHA256 of the raw body with the secret as a UTF-8 string, and compare it with the header in constant time:
const crypto = require('crypto');
const express = require('express');
function verifyTriggerSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const received = Buffer.from(signatureHeader, 'utf8');
const computed = Buffer.from(expected, 'utf8');
return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}
const app = express();
app.post('/webhooks/trigger-dev', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyTriggerSignature(req.body, req.get('x-trigger-signature-hmacsha256'), process.env.TRIGGER_DEV_WEBHOOK_SECRET)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body.toString('utf8'));
// Route on event.type, deduplicate on event.id, then process asynchronously.
res.sendStatus(200);
});
The same check in Python:
import hashlib
import hmac
def verify_trigger_signature(raw_body: bytes, signature_header, secret: str) -> bool:
if not signature_header or not secret:
return False
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature_header.encode("utf-8"), expected.encode("utf-8"))
The secret is a plain string: do not base64-decode it or strip a prefix. Nothing but the body is signed, so there is no timestamp to check.
Make Trigger.dev webhooks production-ready. Hookdeck Event Gateway verifies the
x-trigger-signature-hmacsha256header, stores every run, deployment and error alert, and routes each one by environment or type to the right destination.
Trigger.dev webhook limitations and pain points
Error group alerts go quiet
The Problem: A task has been failing in production for days, and no alert has arrived.
Why It Happens: An error group alert fires only when a group's status changes: a new issue, a regression after it was resolved, or a breach of an ignore condition. After it fires, the group is Unresolved and stays silent however many more times the error occurs. Trigger.dev documents this as intended behaviour.
Workarounds:
- Create a run fails alert as well. It fires for every run that fails once its retries are exhausted.
- Resolve or ignore error groups once you have dealt with them, so a recurrence alerts again.
How Hookdeck Can Help: Send both alert types to one Hookdeck source and use filters on type and object.environment.type to route run failures, error groups and deployment results to different destinations.
No documented delivery guarantees
The Problem: If your endpoint is down when a deployment fails, you may never hear about it.
Why It Happens: Trigger.dev documents no retry policy, timeout or success criterion for alert webhooks.
Workarounds:
- Keep the endpoint simple and fast, and return
200as soon as the signature checks out. - Keep email or Slack alerts enabled for the alerts that matter most.
How Hookdeck Can Help: Hookdeck accepts each alert as soon as it arrives and stores it, then delivers to your service with automatic retries and issue notifications when delivery fails.
Verification details live in the SDK, not the docs
The Problem: You cannot find the header name or algorithm in Trigger.dev's documentation when writing a verifier outside Node.
Why It Happens: The docs show only webhooks.constructEvent(). The header (x-trigger-signature-hmacsha256), the algorithm and the hex encoding are defined in the SDK source.
Workarounds:
- Use the manual verifiers above, which match the SDK.
- Test against a captured delivery:
printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET"should print the header value.
How Hookdeck Can Help: Hookdeck's Trigger.dev source verifies the header for you, so services in any language can trust what arrives from Hookdeck.
Best practices
Verify against the raw body
In Express, use express.raw({ type: '*/*' }). In Next.js, use await request.text() or pass the untouched Request to the SDK, which consumes the body itself. In FastAPI, use await request.body().
Deduplicate on id
Every alert has a unique id. Store it before you open a ticket or page someone, so a repeated delivery does not create a second incident. See our guide to webhook idempotency.
Route by environment
Use object.environment.type on run and deployment alerts to treat PRODUCTION failures differently from STAGING and PREVIEW.
Acknowledge fast and process asynchronously
Return 200 once the signature matches and the alert is stored, then create tickets or post to chat afterwards. See why to process webhooks asynchronously.
Handle unknown types
Log and acknowledge any type you do not recognise rather than failing, so a new alert type never shows up as a failed delivery.
Conclusion
Trigger.dev alert webhooks cover failed runs, deployments and error groups, and each is signed with a bare hex HMAC-SHA256 of the raw body in x-trigger-signature-hmacsha256. Verify with the SDK or by hand, route on type and environment, deduplicate on id, and pair error group alerts with run failure alerts so a persistent failure does not go unnoticed.
Hookdeck Event Gateway verifies every Trigger.dev alert, stores it, and routes it to the right team, so an alert is not lost when your receiver is down.
Get started with Hookdeck for free and handle Trigger.dev webhooks reliably in minutes.