Gareth Wilson Gareth Wilson

Guide to GoCardless Webhooks: Features and Best Practices

Published · Updated


GoCardless webhooks notify your application about bank debit activity: a mandate is created or cancelled, a payment is confirmed or fails, a payout is paid. If you're building on GoCardless for bank debit or open banking payments, webhooks are how you react to these events without polling.

This guide covers how GoCardless webhooks work, the event batches you'll handle, how to verify the Webhook-Signature, and the best practices for production.

What are GoCardless webhooks?

GoCardless webhooks are JSON POSTs delivered to a URL you configure. A single POST carries an array of events (events: [...]) with up to 250 events per request. Each is signed with a Webhook-Signature header (no X- prefix, no scheme prefix in the value), computed over the raw POST body with your webhook endpoint's secret. GoCardless doesn't name the algorithm in its own prose, but it's HMAC-SHA256: the value is a 64-character lowercase hex digest, which both GoCardless's official Node SDK computes that way and reproduces exactly when you verify a real delivery. Use the endpoint secret verbatim as the HMAC key, don't base64-decode it. The official Node SDK also verifies for you.

GoCardless webhook features

FeatureDetails
ConfigurationDevelopers > Webhook endpoints
Signature headerWebhook-Signature (no prefix)
Signature schemeHMAC-SHA256 (hex) over the raw body, keyed with the endpoint secret used verbatim; the SDK computes it
EnvelopeAn array events: [...], up to 250 events per POST
Event fieldsresource_type and action are separate fields (not a dotted name)
RetriesNo automatic schedule documented; manual POST /webhooks/{id}/actions/retry
Invalid signatureDocs recommend replying 498
SDKnpm gocardless-nodejs (verifies); pip gocardless_pro

Common events

Each element carries resource_type and action as separate fields (there's no single dotted payments.confirmed name). Confirmed pairs:

resource_type / actionFires when
mandates / created, cancelled, customer_approval_grantedA mandate is created, cancelled, or approved
payments / created, submitted, confirmed, paid_outA payment progresses
payments / failed, charged_back, cancelled, resubmission_requestedA payment has trouble
payouts / paidA payout is paid

Each element also carries details{origin, cause, description, scheme, reason_code} and links{}. Dispatch on the resource_type + action combination.

Setting up GoCardless webhooks

In the dashboard, go to Developers > Webhook endpoints, add your HTTPS URL, and copy the endpoint secret into GOCARDLESS_WEBHOOK_SECRET. Use the durable docs at docs.gocardless.com (the legacy developer.gocardless.com is deprecating on 24 Aug 2026).

Securing GoCardless webhooks

In Node, use the official gocardless-nodejs/webhooks parse(), which verifies the Webhook-Signature over the raw body and returns the events array (throwing InvalidSignatureError on a mismatch). In other languages, compute a keyed hash over the raw body with your secret and compare in constant time. Verify against the raw body, re-serializing changes the bytes. The docs recommend replying 498 to an invalid signature.

const { parse, InvalidSignatureError } = require("gocardless-nodejs/webhooks");

const SECRET = process.env.GOCARDLESS_WEBHOOK_SECRET;

app.post("/webhooks/gocardless", express.raw({ type: "application/json" }), (req, res) => {
  let events;
  try {
    events = parse(req.body, SECRET, req.headers["webhook-signature"]); // raw body
  } catch (err) {
    if (err instanceof InvalidSignatureError) return res.sendStatus(498); // recommended
    return res.sendStatus(400);
  }

  res.sendStatus(204); // acknowledge the whole batch
  for (const event of events) {
    processQueue.add(event); // dedupe on event.id, dispatch on resource_type + action
  }
});

The same verification in Python (manual HMAC-SHA256 over the raw body). Use the endpoint secret verbatim as the key:

import hmac
import hashlib
import os

SECRET = os.environ["GOCARDLESS_WEBHOOK_SECRET"].encode()


def verify(raw_body: bytes, signature_header: str) -> bool:
    expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")
    # then json.loads(raw_body)["events"] is an array of up to 250 events

GoCardless webhook limitations and pain points

The body is an array of up to 250 events

The Problem: A single POST carries events: [...] with up to 250 events. A handler written for a single-event body silently drops the other 249.

Why It Happens: GoCardless batches events into one delivery.

Workarounds:

  • Iterate the events array and process each element, then acknowledge once.

How Hookdeck Can Help: Hookdeck can fan a batch out into individual events, so each is delivered, retried, and observed on its own.

The algorithm isn't named in the docs

The Problem: GoCardless never names the signing algorithm in its prose, only that the signature is computed from the body and the endpoint secret. From the docs alone it isn't obvious which scheme to implement.

Why It Happens: The docs describe the signature by its inputs, not by name.

Workarounds:

  • It's HMAC-SHA256, hex, over the raw body, with the endpoint secret used verbatim as the key (confirmed both by GoCardless's official SDK and by reproducing the Webhook-Signature on a live delivery). Use the official Node SDK, or that scheme directly.

How Hookdeck Can Help: Hookdeck verifies GoCardless deliveries at the edge, so you don't have to reconstruct the scheme from the docs.

There's no automatic retry

The Problem: GoCardless documents no automatic retry schedule. If your endpoint is down, events aren't retried on a timer, you retry them manually.

Why It Happens: GoCardless exposes a manual retry endpoint (POST /webhooks/{id}/actions/retry) rather than an automatic backoff.

Workarounds:

  • Acknowledge reliably, and reconcile missed events via the API or the manual retry endpoint.

How Hookdeck Can Help: Hookdeck durably queues events and retries on its own schedule, so a downstream outage doesn't require manual recovery.

Separate fields, and the 498 response

The Problem: Events identify themselves with separate resource_type and action fields, not a dotted name, and the docs recommend the non-standard 498 status for an invalid signature. Handlers keyed to dotted names, or returning a standard 4xx, get it wrong.

Why It Happens: GoCardless models events as a resource plus an action and adopted 498 for signature failures.

Workarounds:

  • Dispatch on resource_type + action, and reply 498 on a bad signature.

How Hookdeck Can Help: Hookdeck's filters route on the fields you receive, so your app isn't coupled to a naming convention.

Best practices

Iterate the events array

Loop over events (up to 250) and process each; acknowledge the batch once.

Verify with the official SDK

Use gocardless-nodejs/webhooks parse() in Node; treat a manual HMAC-SHA256 as inferred and confirm it against a real delivery.

Dedupe on event.id and reply 498 on failure

Event creation is asynchronous, so dedupe on event.id; reply 498 to an invalid signature.

Acknowledge fast, process asynchronously

Return 2xx (204) quickly and defer work to a queue. See why to process webhooks asynchronously.

Make GoCardless webhooks production-ready

Hookdeck verifies Webhook-Signature, fans out batches, deduplicates, and durably queues every event

Conclusion

GoCardless delivers webhooks as an array of up to 250 events, verified with a Webhook-Signature HMAC-SHA256 (hex) over the raw body, keyed with the endpoint secret used verbatim (the Node SDK computes it). Iterate the array, dispatch on the separate resource_type and action fields, dedupe on event.id, reply 498 on a bad signature, and reconcile via the API since there's no automatic retry.

Hookdeck verifies the signature, fans out batches, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.

Get started with Hookdeck for free and handle GoCardless webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.