$npx -y skills add hookdeck/webhook-skills --skill gocardless-webhooksReceive and verify GoCardless webhooks. Use when setting up GoCardless webhook handlers, debugging Webhook-Signature verification, or handling bank debit events like payments confirmed, payments failed, mandates cancelled, and payouts paid.
| 1 | # GoCardless Webhooks |
| 2 | |
| 3 | GoCardless is a bank debit / recurring payments platform. It sends webhooks as |
| 4 | **batches** of events (up to 250 per request) in an `events` array, signed with an |
| 5 | HMAC-SHA256 signature in the `Webhook-Signature` header. |
| 6 | |
| 7 | ## When to Use This Skill |
| 8 | |
| 9 | - How do I receive GoCardless webhooks? |
| 10 | - How do I verify the GoCardless `Webhook-Signature` header? |
| 11 | - Why is my GoCardless webhook signature verification failing? |
| 12 | - How do I handle `payments` `confirmed`/`failed`, `mandates` `cancelled`, or `payouts` `paid` events? |
| 13 | - How do I process the GoCardless `events` array idempotently? |
| 14 | |
| 15 | ## How GoCardless Signs Webhooks |
| 16 | |
| 17 | - **Header:** `Webhook-Signature` |
| 18 | - **Algorithm:** HMAC-SHA256 over the **raw request body**, keyed with the |
| 19 | webhook endpoint secret (from your GoCardless Dashboard) |
| 20 | - **Encoding:** lowercase **hex** string |
| 21 | - **Comparison:** timing-safe equality |
| 22 | - **Response:** return `204 No Content` once the whole batch is accepted. Return a |
| 23 | non-2xx (e.g. `498`) if verification fails. GoCardless **retries the whole batch** |
| 24 | on any non-2xx, so event handlers must be **idempotent on `event.id`**. |
| 25 | |
| 26 | Always verify against the **raw body** — parsing JSON first and re-serializing will |
| 27 | change the bytes and break the signature. |
| 28 | |
| 29 | ## Verification (core) |
| 30 | |
| 31 | Use the official `gocardless-nodejs` SDK where it runs (Node.js). `parse()` verifies |
| 32 | the signature (timing-safe) and returns the events array, throwing |
| 33 | `InvalidSignatureError` when the signature does not match. |
| 34 | |
| 35 | ```javascript |
| 36 | // Node.js — official SDK (gocardless-nodejs), req.body is the RAW Buffer |
| 37 | const { parse, InvalidSignatureError } = require('gocardless-nodejs/webhooks'); |
| 38 | |
| 39 | try { |
| 40 | const events = parse( |
| 41 | req.body, // raw body (Buffer/string), NOT parsed JSON |
| 42 | process.env.GOCARDLESS_WEBHOOK_SECRET, // webhook endpoint secret |
| 43 | req.headers['webhook-signature'] // Webhook-Signature header |
| 44 | ); |
| 45 | // signature valid — process each event, then respond 204 |
| 46 | } catch (err) { |
| 47 | if (err instanceof InvalidSignatureError) { |
| 48 | // signature mismatch — respond 498 (do not process) |
| 49 | } |
| 50 | } |
| 51 | ``` |
| 52 | |
| 53 | For languages without a GoCardless SDK (e.g. Python/FastAPI), verify manually — same |
| 54 | algorithm, timing-safe compare: |
| 55 | |
| 56 | ```python |
| 57 | import hmac, hashlib |
| 58 | expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() |
| 59 | valid = hmac.compare_digest(expected, signature_header) # timing-safe |
| 60 | ``` |
| 61 | |
| 62 | > **For complete handlers with tests**, see [examples/express/](examples/express/), [examples/nextjs/](examples/nextjs/), [examples/fastapi/](examples/fastapi/). |
| 63 | |
| 64 | ## Common Event Types |
| 65 | |
| 66 | GoCardless events combine a `resource_type` with an `action`. The most common: |
| 67 | |
| 68 | | resource_type | action | Triggered When | |
| 69 | |---------------|--------|----------------| |
| 70 | | `payments` | `confirmed` | Funds confirmed collected from the customer | |
| 71 | | `payments` | `paid_out` | Payment included in a payout to your bank account | |
| 72 | | `payments` | `failed` | Payment failed (e.g. insufficient funds) | |
| 73 | | `payments` | `cancelled` | Payment cancelled before submission | |
| 74 | | `payments` | `charged_back` | Customer charged the payment back | |
| 75 | | `mandates` | `active` | Mandate set up and ready to collect | |
| 76 | | `mandates` | `cancelled` | Mandate cancelled (e.g. bank account closed) | |
| 77 | | `mandates` | `failed` | Mandate setup failed | |
| 78 | | `mandates` | `expired` | Mandate expired through inactivity | |
| 79 | | `payouts` | `paid` | Payout sent to your bank account | |
| 80 | | `refunds` | `paid` | Refund submitted to the customer | |
| 81 | | `refunds` | `failed` | Refund failed | |
| 82 | | `subscriptions` | `created` | Subscription created | |
| 83 | | `subscriptions` | `cancelled` | Subscription cancelled | |
| 84 | |
| 85 | See [overview.md](references/overview.md) for the full action list per resource type. |
| 86 | |
| 87 | ## Environment Variables |
| 88 | |
| 89 | ```bash |
| 90 | # Webhook endpoint secret from the GoCardless Dashboard (Developers → Webhook endpoints) |
| 91 | GOCARDLESS_WEBHOOK_SECRET=your_webhook_endpoint_secret |
| 92 | ``` |
| 93 | |
| 94 | ## Local Development |
| 95 | |
| 96 | For local webhook testing, run the Hookdeck CLI via `npx` — no install required: |
| 97 | |
| 98 | ```bash |
| 99 | npx hookdeck-cli listen 3000 gocardless --path /webhooks/gocardless |
| 100 | ``` |
| 101 | |
| 102 | No account required. The CLI creates a guest account on first run and provides a |
| 103 | local tunnel + web UI for inspecting requests. Use port `8000` for the FastAPI example. |
| 104 | |
| 105 | ## Reference Materials |
| 106 | |
| 107 | - [Overview](references/overview.md) - What GoCardless webhooks are, full event/action list |
| 108 | - [Setup](references/setup.md) - Create a webhook endpoint and copy the secret |
| 109 | - [Verification](references/verification.md) - Signature verification details and gotchas |
| 110 | |
| 111 | ## Examples |