Agent skill
GC Notify Webhooks Skill
Receive and authenticate GC Notify webhooks (Government of Canada notification service callbacks). Use when setting up a GC Notify callback endpoint, checking the Authorization: Bearer token, debugging 401s, or handling email and SMS delivery receipts with statuses like delivered, permanent-failure, temporary-failure and technical-failure.
Install this skill
npx skills add hookdeck/webhook-skills --skill gc-notify-webhooks
GC Notify is the Government of Canada's notification service (a fork of GOV.UK Notify, but a separate service). When an email or text message you sent is delivered or fails, GC Notify POSTs a JSON delivery receipt to the callback URL you registered. Callbacks are not signed. You set a bearer token for the callback, and GC Notify sends it on every request as Authorization: Bearer <token>. Your endpoint authenticates the request by comparing it with the token you stored.
When to Use This Skill
- How do I receive GC Notify callbacks (webhooks)?
- How do I verify the
Authorization: Bearertoken on a GC Notify callback? - Does GC Notify sign webhook payloads? (No: it sends a static bearer token)
- How do I handle
delivered,permanent-failureortechnical-failuredelivery receipts? - Why does my GC Notify callback endpoint return 401?
Verification (core)
const crypto = require('crypto');
const BEARER = /^bearer /i; // auth schemes are case-insensitive (RFC 9110)
function safeEqual(a, b) { // Buffers; hash both sides so lengths match
const ha = crypto.createHash('sha256').update(a).digest();
const hb = crypto.createHash('sha256').update(b).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyGcNotifyRequest(headers, configured = process.env.GC_NOTIFY_WEBHOOK_SECRET) {
const secret = configured ? configured.replace(BEARER, '') : ''; // bare token; never trim
if (!secret) return false; // never accept when unconfigured
const header = headers['authorization'];
if (typeof header !== 'string' || !BEARER.test(header)) return false;
return safeEqual(Buffer.from(header.slice(7), 'latin1'), Buffer.from(secret, 'utf8'));
}
- Header:
Authorization: Bearer <token>. Store just the token inGC_NOTIFY_WEBHOOK_SECRET. - Return
401for a missing header, a scheme other thanBearer, or a mismatch. - Read the body raw (
express.raw({ type: '*/*' })) so any content type works, and parse it only after the check. - Limits of this check: a static shared credential authenticates the sender, not the body, and gives no replay protection (there is no signature and no timestamp). Use HTTPS and rotate the bearer token in GC Notify if it leaks.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Hookdeck Setup Note
When you create the GC Notify source in Hookdeck (source type GC_NOTIFY, labelled "GC Notify"), it shows this note: "Paste the bearer token you set for the callback in GC Notify." Paste only the token; Hookdeck matches the Bearer scheme itself.
Delivery Receipt Statuses
Callbacks have no event field. Every body is a delivery receipt; dispatch on status. All values are strings.
The docs list 10 statuses but only 9 descriptions, so they don't pair one-to-one; use status, not status_description. Matched by meaning and notification-api's labels:
status | Meaning (status_description) |
|---|---|
created, sending, pending | In transit |
sent | Not paired in the docs; notification-api labels it Delivered for email, Sent for SMS |
delivered | Delivered |
permanent-failure | Blocked, No such number, or No such address |
temporary-failure | Content or inbox issue, or Carrier issue |
technical-failure | Tech issue (provider_response has detail) |
pending-virus-check | In transit |
virus-scan-failed | Attachment has virus |
Other fields: id, reference, to, status_description, provider_response, created_at, completed_at, sent_at and notification_type (email or sms). A notification can produce several callbacks (for example delivered, then a bounce), so handle them idempotently.
GC Notify also POSTs {"health_check": "true"} when you set up or update callbacks; the examples acknowledge it with 200.
Environment Variables
GC_NOTIFY_WEBHOOK_SECRET=your-callback-bearer-token # the token you set for the callback
Delivery Behaviour
Respond within 1 second (GC Notify's guidance). A failed callback is retried 6 times over about 25 minutes; after repeated errors GC Notify suspends callbacks for your service and emails you, and you must contact them to resume.
Local Development
GC Notify can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 gc-notify --path /webhooks/gc-notify
Use the printed URL as the callback URL in GC Notify.
Resources
- references/overview.md - What GC Notify callbacks are, statuses and payload shape
- references/setup.md - Register the callback URL and bearer token
- references/verification.md - Bearer check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests