Agent skill
GOV.UK Notify Webhooks Skill
Receive and authenticate GOV.UK Notify callbacks. Use when setting up a GOV.UK Notify callback endpoint, checking the Authorization Bearer token, debugging 401s, or handling delivery receipts (delivered, permanent-failure, temporary-failure, technical-failure), received text messages and returned letters.
Install this skill
npx skills add hookdeck/webhook-skills --skill govuk-notify-webhooks
GOV.UK Notify posts JSON callbacks to your service for three things: delivery receipts (email and text message status), received text messages and returned letters. Callbacks are not signed. You give Notify a callback URL and a bearer token, and Notify sends the token as Authorization: Bearer <token> on every request. Your endpoint authenticates the request by comparing that header with the token you chose.
When to Use This Skill
- How do I receive GOV.UK Notify callbacks?
- How do I verify the
Authorization: Bearertoken Notify sends? - Does GOV.UK Notify sign webhook payloads? (No: shared bearer token only)
- How do I tell delivery receipts from received text messages? (There is no event field)
- Why does my Notify callback endpoint return 401?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyNotifyRequest(headers, secret = process.env.GOVUK_NOTIFY_WEBHOOK_SECRET) {
if (!secret) return false; // never accept when unconfigured
const expected = secret.toLowerCase().startsWith('bearer ') ? secret.slice(7) : secret;
const header = headers['authorization'];
if (!expected || typeof header !== 'string' || !header.toLowerCase().startsWith('bearer ')) return false;
return safeEqual(header.slice(7), expected); // token itself is case-sensitive
}
- Header:
Authorization: Bearer <token>. Match theBearerscheme case-insensitively (RFC 9110); compare the rest to the secret in constant time. Don't trim or alter the secret. GOVUK_holds the bare token. If the configured value itself starts withNOTIFY_ WEBHOOK_ SECRET Bearer(any case), strip it, as Hookdeck does.- Missing header, wrong scheme or mismatch:
401. Body that isn't a JSON object:400. Handled:200. - Read the body raw (
express.raw({ type: '*/*' })) and authenticate before parsing it. - A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS and rotate the token in Notify if it leaks.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Callback Types
Payloads have no event field. Tell the kinds apart by shape (documented field tables):
| Callback | Identifying fields | Sent When |
|---|---|---|
| Delivery receipt | status, notification_type, id, reference, to | An email or text message changes status |
| Received text message | source_number, destination_number, message, date_received | Someone texts your Notify number |
| Returned letter | notification_id, date_sent, template_name | A letter you sent is returned |
Delivery receipt status values: delivered, permanent-failure, temporary-failure, technical-failure. notification_type is email or sms. If a request fails, Notify retries every 5 minutes, up to 5 times, so handle duplicates idempotently.
Environment Variables
GOVUK_NOTIFY_WEBHOOK_SECRET=your-callback-bearer-token # the token you set for the callback
Hookdeck Setup
Hookdeck's source type is GOV.UK Notify (GOVUK_NOTIFY): API key verification on the authorization header with a Bearer prefix. Paste the bearer token you set for the callback in GOV.UK Notify (API integration > Callbacks).
Local Development
Notify can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 govuk-notify --path /webhooks/govuk-notify
Use the printed URL as the callback URL in Notify.
Resources
- references/overview.md - What Notify callbacks are, payload shapes
- references/setup.md - Configure the callback and bearer token
- references/verification.md - Bearer check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests