Agent skill
Lob Webhooks Skill
Receive and verify Lob webhooks. Use when setting up Lob print-and-mail webhook handlers, debugging Lob-Signature verification, or handling events like postcard.delivered, letter.mailed, check.in_transit, and self_mailer.rendered_thumbnails.
Install this skill
npx skills add hookdeck/webhook-skills --skill lob-webhooks
When to Use This Skill
- How do I receive Lob webhooks?
- How do I verify the
Lob-Signatureheader (HMAC-SHA256 overLob-Signature-Timestamp+.+ body)? - How do I handle
postcard.delivered,letter.mailed, orcheck.in_transitevents? - Why does my Lob webhook signature verification fail for events from the webhook debugger?
- How do I add optional replay protection with
Lob-Signature-Timestamp?
Setup note
Events sent from Lob's webhook debugger are signed with a fixed test secret, so they fail verification. Real deliveries are signed with the webhook's secret.
Verification (core)
Lob has no webhook SDK, so verify manually. Lob-Signature is the bare lowercase hex HMAC-SHA256 digest (no prefix), keyed with the webhook's secret (its UTF-8 bytes, as-is), over the bytes of: the Lob-Signature-Timestamp header value, a literal ., then the raw request body. Never parse and re-serialize the body before verifying.
const crypto = require('crypto');
function verifyLob(rawBody /* Buffer */, headers, secret) {
const signature = headers['lob-signature'];
const timestamp = headers['lob-signature-timestamp'];
if (!secret || !signature || !timestamp) return false;
if (!/^[0-9a-fA-F]{64}$/.test(signature)) return false; // validate before decoding
const message = Buffer.concat([
Buffer.from(timestamp, 'latin1'), // header exactly as received
Buffer.from('.'),
rawBody, // raw bytes, never a re-encoded string
]);
const expected = crypto.createHmac('sha256', secret).update(message).digest();
const received = Buffer.from(signature.toLowerCase(), 'hex'); // hex is case-insensitive
return expected.length === received.length && crypto.timingSafeEqual(expected, received);
}
Respond 401 when a header is missing or empty, the signature is malformed, or it does not match. In Express use express.raw({ type: '*/*' }) so the exact bytes are verified whatever the content type.
Timestamp tolerance is off by default. Lob's step 4 (reject old timestamps, 5 minutes recommended) is optional. Hookdeck retries and replays re-send the original headers and timestamp, and Hookdeck itself applies no window, so a fixed window would reject legitimate retries. The examples offer an opt-in LOB_ (unset or 0 = off). Deduplicate on the event id instead.
For complete handlers with route wiring, event dispatch, and tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Common Event Types
Lob delivers the entire event object as a POST. Dispatch on event_type.id; the mailpiece or other resource is in the event's body. There are 93 event types in all (see Lob's event types).
event_type.id | Triggered When |
|---|---|
postcard.created | A postcard is created |
postcard.delivered | A postcard is delivered (USPS tracking event) |
postcard.re-routed | A postcard is re-routed (note the hyphen) |
letter.mailed | A letter is mailed |
letter.delivered | A letter is delivered |
check.in_transit | A check is in transit |
self_ | Thumbnails for a self mailer have been rendered |
For the payload shape and more events, see references/overview.md.
Important Headers
| Header | Description |
|---|---|
Lob-Signature | Lowercase hex HMAC-SHA256 digest, no prefix |
Lob-Signature-Timestamp | Timestamp that is part of the signed input (unit not documented) |
Environment Variables
LOB_WEBHOOK_SECRET=your_lob_webhook_secret # webhook details page in the Lob dashboard
LOB_WEBHOOK_TOLERANCE_SECONDS=0 # optional; unset or 0 = no timestamp check
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 lob --path /webhooks/lob
Reference Materials
- references/overview.md - Lob webhook concepts, events, retries
- references/setup.md - Dashboard configuration and secret
- references/verification.md - Signature verification details and gotchas