Files
Eric Allam a7c0e8b740 docs(webhooks): fix typed-source example, idempotency fallback, and url-secret warning
The typed-event example now passes webhooks.stripe<Stripe.Event>() to webhook()
so it actually demonstrates typed access to event. The verification section no
longer claims every idempotency key comes from a provider event id (it documents
the raw-body/timestamp/signature fallback), and adds a warning that url-secret
exposes the secret in the URL.
2026-08-16 19:35:32 +01:00

134 lines
4.3 KiB
Plaintext

---
title: "Sources and verification"
description: "Provider presets, custom verification config, and typing the webhook event."
sidebarTitle: "Sources & verification"
---
A webhook's `source` tells Trigger.dev which provider the request is from and how to verify it. Use a built-in preset, or `webhooks.custom()` for a provider without one.
## Presets
Built-in presets know the provider's signature scheme, so you don't configure anything:
<CodeGroup>
```ts Stripe
import { webhook, webhooks } from "@trigger.dev/sdk";
export const stripeWebhook = webhook({
id: "stripe",
source: webhooks.stripe(),
onEvent: async ({ event }) => {
if (event.type === "payment_intent.succeeded") {
// ...
}
},
});
```
```ts GitHub
import { webhook, webhooks } from "@trigger.dev/sdk";
export const githubWebhook = webhook({
id: "github",
source: webhooks.github(),
onEvent: async ({ event, headers }) => {
// GitHub puts the event type in a header
console.log(headers.get("x-github-event"));
},
});
```
```ts Svix
import { webhook, webhooks } from "@trigger.dev/sdk";
// Also covers Clerk, Resend, and other Svix-powered providers
export const svixWebhook = webhook({
id: "svix",
source: webhooks.svix(),
onEvent: async ({ event }) => {
// ...
},
});
```
</CodeGroup>
The available presets are `stripe()`, `github()`, `svix()`, `square()`, and `discord()`.
## Custom providers
For a provider without a preset, `webhooks.custom()` describes the scheme as data. For example, an HMAC-SHA256 signature over the raw body, in a custom header:
```ts
import { webhook, webhooks } from "@trigger.dev/sdk";
export const customWebhook = webhook({
id: "custom",
source: webhooks.custom<{ id: string; message: string }>({
scheme: "hmac",
algorithm: "sha256",
encoding: "hex",
signatureHeader: "x-webhook-signature",
signingString: "raw",
idempotencyField: { from: "body", name: "id" },
}),
onEvent: async ({ event }) => {
console.log(event.message);
},
});
```
<Tip>
Reach for a preset first; drop to `custom()` for the long tail. A custom config can almost always
express a provider's scheme without any code.
</Tip>
## Typing the event
Presets ship a sensible default event type, and they're generic, so you can plug in the provider's official type for full type-safety and autocomplete:
```ts
import type Stripe from "stripe";
import { webhook, webhooks } from "@trigger.dev/sdk";
export const stripeEvents = webhook({
id: "stripe-events",
source: webhooks.stripe<Stripe.Event>(),
onEvent: async ({ event }) => {
// `event` is now the full, discriminated Stripe.Event union
},
});
```
For `webhooks.custom<T>()`, pass your own event type as `T`.
<Note>
The event is typed but not re-validated against that type at runtime: once the signature is
verified, the body is trusted (the same model the official provider SDKs use). If you want runtime
validation, validate `event` inside `onEvent`.
</Note>
## How verification works
Every inbound request is verified before your task runs. Trigger.dev checks the signature, the
timestamp (for replay protection, where the provider supplies one), and derives an idempotency key.
When the preset maps a provider event id it uses that; otherwise it falls back to a hash of the raw
body, timestamp, and signature. A request that fails verification gets a `400` and never creates a run.
Presets handle this for you. Under the hood, every scheme is one of:
- **`hmac`**: HMAC over the raw body or a templated signing string, signature in a header. The header can be a raw value, a prefixed one (like GitHub's `sha256=…`), or a structured one (like Stripe's `t=…,v1=…`).
- **`shared-secret`**: a static token compared in a header, bearer, basic auth, or the body.
- **`url-secret`**: a secret in the URL path or query string.
- **`asymmetric`**: public-key signatures (Ed25519, ECDSA, RSA). You store the provider's public key instead of a shared secret.
<Warning>
`url-secret` places the secret in the request URL (path or query string), where it can be captured
by access logs, proxies, and tracing systems. Prefer a header-based scheme (`hmac` or
`shared-secret`) when the provider supports one.
</Warning>
Once a request is verified, see [Connecting a provider](/webhooks/connect) for how to point the
provider at the webhook URL and set the secret.