# Webhooks Reference

Source: https://docs.gocardless.com/docs/api-reference/webhooks

# Webhooks Reference

Webhooks are HTTP POST requests that GoCardless sends to your server when events occur in your account. Each request contains a batch of one or more [Event](/docs/api-reference/events) objects. For a step-by-step guide to receiving and processing webhooks see [Stay up to date with webhooks](/docs/getting-started/stay-up-to-date-with-webhooks).

## Setting up a webhook endpoint

Register a webhook endpoint from **Developers > Webhooks** in the GoCardless Dashboard. Each endpoint has its own secret, which is used to sign the requests GoCardless sends to it.

Your endpoint URL must use HTTPS. GoCardless requires the full TLS certificate chain, including intermediate certificates. You can validate your server's certificate chain with [Qualys SSL Labs](https://www.ssllabs.com/ssltest/).

## Delivery behaviour

### At least once delivery

GoCardless guarantees that each event will be delivered **at least once** — it does not guarantee exactly-once delivery. In failure and retry scenarios, the same event may appear in more than one webhook. **Your handler must be idempotent**: check whether you have already processed each event ID before acting on it.

> **Warning:**
> Do not rely on the webhook `meta.webhook_id` for deduplication — it identifies the delivery
>   attempt, not the event. Use the individual `event.id` values inside the `events` array instead.

### Out-of-order delivery

Webhooks may arrive out of order. A `payment.confirmed` event may arrive before the `payment.created` event it logically follows. When you receive a webhook, **always fetch the current state of the resource from the API** rather than inferring it from the event sequence.

### Unknown events

GoCardless may add new event types without considering this a breaking change. If you receive an event type your code does not recognise, return `204 No Content` and ignore it — do not return an error, as that would cause GoCardless to retry the webhook unnecessarily.

### Determining what to do

Use the `details.cause` field on each event to determine how to handle it. The `cause` is GoCardless's normalised, scheme-independent key for what triggered the event. Avoid branching on `details.reason_code` — it is bank-specific and inconsistent across schemes.

## Processing a webhook

The recommended flow for handling an incoming webhook:

1. Verify the signature (see below). Return `498` immediately if invalid.
2. Respond with `2xx` as quickly as possible — within 10 seconds.
3. Store the raw webhook body in durable storage (database or queue).
4. Process each event asynchronously in a background job:
   - Check whether you have already processed the event ID.
   - Fetch the current resource state from the API.
   - Act on the event.

> **Warning:**
> GoCardless waits a maximum of **10 seconds** for a response. If your handler times out, the
>   webhook is treated as failed and will be retried. Acknowledge immediately with `2xx` and process
>   asynchronously to avoid this.

## Status codes

Return one of these status codes from your webhook endpoint:

| Code                  | When to return                                                             |
| --------------------- | -------------------------------------------------------------------------- |
| `2xx` (e.g. `200 OK`) | Webhook received and accepted.                                             |
| `498 Token Invalid`   | Signature verification failed. GoCardless will log this and **not** retry. |
| `204 No Content`      | Event type is not recognised. GoCardless will **not** retry.               |

Any other non-2xx response causes GoCardless to retry the webhook.

## Retries

If your endpoint returns a non-2xx response or times out, GoCardless retries automatically:

- **9 total delivery attempts** (1 initial + 8 retries).
- Retries occur at increasing intervals after each failure.
- All delivery attempts and outcomes are visible in the GoCardless Dashboard under **Developers > Webhooks**.
- You can manually trigger a retry from the Dashboard.

## Request format

Webhooks are sent as `POST` requests with a `Content-Type: application/json` body. Each request includes:

| Header              | Value                                                                                 |
| ------------------- | ------------------------------------------------------------------------------------- |
| `Content-Type`      | `application/json`                                                                    |
| `Webhook-Signature` | HMAC SHA256 hex digest of the request body                                            |
| `Origin`            | `https://api.gocardless.com` (live) or `https://api-sandbox.gocardless.com` (sandbox) |

Use the `Origin` header to distinguish live from sandbox webhooks.

## Signature verification

GoCardless signs every webhook using an **HMAC SHA256** hex digest of the raw request body, keyed with your webhook endpoint secret. The digest is sent in the `Webhook-Signature` header.

To verify a webhook:

1. Retrieve your webhook endpoint secret from the GoCardless Dashboard.
2. Compute `HMAC-SHA256(secret, raw_request_body)` as a hex string.
3. Compare the result to the `Webhook-Signature` header value.
4. If they match, the webhook is authentic. If not, return `498 Token Invalid`.

```ruby
require "openssl"

digest = OpenSSL::Digest.new("sha256")
calculated_signature = OpenSSL::HMAC.hexdigest(digest, secret, request_body)

if calculated_signature == request_signature
  # Authentic — proceed
else
  # Invalid — return 498 Token Invalid
end
```

**Important:**

- Always verify the signature **before** reading or acting on the payload.
- Use the **raw request body** — do not parse the JSON and re-serialise it, as this may change the byte sequence and break the digest.
- Each webhook endpoint has its own secret. If you have multiple endpoints, verify each with the correct secret.
- Returning `200` for an invalid signature would cause GoCardless to treat the webhook as successfully delivered. Returning `400` would cause unnecessary retries. Use `498`.

## Dashboard history

Webhooks from the past **3 months** are visible in the GoCardless Dashboard under **Developers > Webhooks**. You can inspect individual delivery attempts, view the payload, and manually retry failed webhooks.

The `meta.webhook_id` in the webhook payload corresponds to the most recent delivery attempt shown in the Dashboard — useful for debugging, but **not** for deduplication.

## IP addresses

GoCardless sends webhooks from these static IP addresses. Add them to your firewall allowlist if your infrastructure restricts inbound traffic by source IP.

| IP Address       |     |
| ---------------- | --- |
| `35.204.73.47`   |     |
| `35.204.191.250` |     |
| `35.204.214.181` |     |

GoCardless will give at least **2 weeks' advance notice** before adding or removing any IP addresses.

## What's next

  
#### [Stay up to date with webhooks](/docs/getting-started/stay-up-to-date-with-webhooks)

Step-by-step guide to building a webhook handler with code samples.

  
#### [Events](/docs/api-reference/events)

Full reference for the Event object and all resource event types.

  
#### [Testing Webhooks](/docs/developer-resources/testing-webhooks-dashboard)

Send test webhook events from the GoCardless Dashboard.

  
#### [IP Addresses](/docs/api-reference/ip-addresses)

Guidance on IP allowlisting for API and webhook traffic.