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: Bearer token 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 the Bearer scheme case-insensitively (RFC 9110); compare the rest to the secret in constant time. Don't trim or alter the secret.
  • GOVUK_NOTIFY_WEBHOOK_SECRET holds the bare token. If the configured value itself starts with 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):

CallbackIdentifying fieldsSent When
Delivery receiptstatus, notification_type, id, reference, toAn email or text message changes status
Received text messagesource_number, destination_number, message, date_receivedSomeone texts your Notify number
Returned letternotification_id, date_sent, template_nameA 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 10, 2026

View on GitHub →