# 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: Bearer` token on a GC Notify callback?
* Does GC Notify sign webhook payloads? (No: it sends a static bearer token)
* How do I handle `delivered`, `permanent-failure` or `technical-failure` delivery receipts?
* Why does my GC Notify callback endpoint return 401?

## Verification (core)

```javascript
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 in `GC_NOTIFY_WEBHOOK_SECRET`.
* Return `401` for a missing header, a scheme other than `Bearer`, 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/](https://github.com/hookdeck/webhook-skills/tree/main/skills/gc-notify-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/gc-notify-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/gc-notify-webhooks/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

```bash
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):

```bash
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](https://github.com/hookdeck/webhook-skills/blob/main/skills/gc-notify-webhooks/references/overview.md) - What GC Notify callbacks are, statuses and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/gc-notify-webhooks/references/setup.md) - Register the callback URL and bearer token
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/gc-notify-webhooks/references/verification.md) - Bearer check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/gc-notify-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests