Skip to main content
GoCardless API · invalid_api_usage · 429

GoCardless 429 rate_limit_exceeded: too many requests per minute

GoCardless limits the requests it accepts per minute. Anyone who goes over receives a 429 with the reason rate_limit_exceeded and a header that says when they can continue. The connector doesn't show you that the first time: it waits for the time GoCardless asks for and repeats the request once. Only if it hits the limit again will you see the error in Odoo.

FlexigoTech · Editor in charge: Víctor Gómez · Updated 6 November 2026

Key facts

Versions
Odoo 17, 18, 19 and 20
Checked
in the connector's source code, version 17.0, 18.0, 19.0 and 20.0 · against the sources linked at the end
Reading time
4 min

What GoCardless says

ReasonTypeHTTPGoCardless text
rate_limit_exceededinvalid_api_usage429Rate limit reached. Check headers for reset time.

GoCardless's rate limits page gives the figure: 1,600 requests per minute on a standard integration and 1,600 per minute and merchant on a partner integration. When that is exceeded, the API returns the 429, and GoCardless notes that these requests are safe to retry: “These requests are safe to retry.”. To know when, it points to the response headers: ratelimit-limit, ratelimit-remaining and ratelimit-reset.

GoCardless rate_limit_exceeded error card
The reason, the HTTP 429 code, the ratelimit-reset header and the 1,600 requests per minute limit published by GoCardless.

Where so many requests come from

The limit counts every request to the API, not just those that create payments. With the connector, each payment against a stored mandate is one request, whether it comes from a subscription renewal or a payment registered with the customer's payment method. Each event for a payment that reaches Odoo is another, because the connector doesn't rely only on the notification: it requests that payment from GoCardless and applies the status it returns. A large batch at the same time, such as many renewals at once while events for earlier payments arrive, adds up both.

GoCardless classifies this reason among the invalid_api_usage errors, those caused by how the request is made, and for those it asks you to alert your dev team. A single 429 doesn't need that; one that repeats every day does.

What the connector does

  1. It reads the ratelimit-reset header and works out how long is left: the time it gives minus the Odoo server's time.
  2. It waits that time, at least 1 second and at most 65. If the header is missing or can't be read, it waits 1 second. The cap prevents a wrongly set clock from leaving the Odoo process waiting forever.
  3. It repeats the request only once.
  4. If the repeat also gets a 429, it no longer waits: it treats it like any other rejection from the API.

The connector doesn't count its requests or throttle them in advance. It doesn't read ratelimit-remaining: it only waits when GoCardless has already replied with a 429.

Diagram: what GoCardless replies with a 429 and what Odoo does with the connector
Diagram based on the connector's code (17.0, 18.0, 19.0 and 20.0) and on GoCardless's documentation.

If the second attempt also hits the limit

It depends on what Odoo was doing at that moment.

  • If it was a payment, the transaction goes to “Error” with “GoCardless rejected the request.” and, below it, GoCardless's message with rate_limit_exceeded and the request_id in square brackets. The invoice remains pending payment.
  • If it was applying a GoCardless event, the webhook answers 500 so that GoCardless sends the notification again, and events already applied are not applied twice. The hourly recovery also doesn't leave behind an event that failed: it requests it again on the next pass.

A 429 is not the same as a payment with no response. Without a response, the connector doesn't know whether GoCardless created the payment, so it leaves it pending and sends it again, with the same key, in less than an hour. With a 429 there is a response: GoCardless did not accept the request, which is why the transaction is left in error.

The connector writes these texts in English, so in an English-language Odoo they appear exactly as quoted here, whether a user or a scheduled action triggers them: “GoCardless rejected the request.” Here the message is the same whoever launches the payment, including a scheduled action such as a subscription renewal.

What to do

  • Open the transaction from the invoice, with the “Payment Transaction” button at the top; the transactions menu only appears in developer mode, and it doesn't exist in Odoo 20. If the message names rate_limit_exceeded, GoCardless rejected that request because of the limit, not because of the customer or their mandate.
  • After a while, charge the invoice again with the customer's stored payment method.
  • If it happens often, check the Odoo server time: the wait is calculated by subtracting it from the ratelimit-reset time, and a clock running fast shortens it.
  • If you need GoCardless, save the request_id from the message: its documentation asks you to include it when contacting its support.

Does the connector spread out requests to avoid reaching the limit?

No. It doesn't count or throttle them in advance: it reacts when GoCardless replies with a 429, with one wait and a single retry.

Why does it wait at most 65 seconds?

While it waits, the Odoo process making the request does nothing else. The connector's code sets that cap so that a wrongly set clock doesn't leave it stuck.

Is any GoCardless event lost because of the limit?

It isn't considered lost: the webhook asks GoCardless to send it again, and the hourly recovery requests it again on the next pass.

With our connector

In Odoo 17, 18, 19 and 20 the connector treats the 429 as a pause: it waits for the time GoCardless asks for, repeats once and, if the limit persists, leaves the transaction in error with the request_id so you know what happened and what to give GoCardless.

Keep reading

GoCardless 409 idempotent_creation_conflict: the payment already existedGoCardless returns 409 idempotent_creation_conflict when an idempotency key already created something. Why that is fine: it prevents a double charge.GoCardless webhook: 498 Token Invalid, 204 and the Webhook-Signature headerGoCardless stops retrying on 498 (signature mismatch) and 204 (unknown event). What Odoo 17, 18, 19 and 20 reply, and why Odoo 20 sends a 401.GoCardless mandate_is_inactive error when creating a collection from OdooGoCardless returns mandate_is_inactive when a payment is created against a cancelled, failed or expired mandate. Why it happens and how Odoo handles it.
Checked in the connector's source code (version 17.0, 18.0, 19.0 and 20.0) and the linked sources

Sources

  • docs.gocardless.com/docs/api-reference/limits
  • docs.gocardless.com/docs/api-reference/responses-and-errors
  • Module source code: flexigo_payment_gocardless: const.py, line 113 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_provider.py, line 378 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_provider.py, line 408 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_provider.py, line 379 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_provider.py, line 383 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_provider.py, line 397 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_provider.py, line 462 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_transaction.py, line 281 in 17.0 (d203ee6), line 281 in 18.0 (e2ab18b), line 281 in 19.0 (ce7f5a3) and line 292 in 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_transaction.py, line 247 in 17.0 (d203ee6), line 247 in 18.0 (e2ab18b), line 247 in 19.0 (ce7f5a3) and line 258 in 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_transaction.py, line 299 in 17.0 (d203ee6), line 299 in 18.0 (e2ab18b), line 299 in 19.0 (ce7f5a3) and line 310 in 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_transaction.py, line 589 in 17.0 (d203ee6), line 589 in 18.0 (e2ab18b), line 588 in 19.0 (ce7f5a3) and line 607 in 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: controllers/main.py, line 124 in 17.0 (d203ee6), line 124 in 18.0 (e2ab18b), line 124 in 19.0 (ce7f5a3) and line 127 in 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: controllers/main.py, line 96 in 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/gocardless_webhook_event.py, line 7 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: const.py, line 117 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)
  • Module source code: flexigo_payment_gocardless: models/payment_provider.py, line 603 in 17.0 (d203ee6), line 603 in 18.0 (e2ab18b), line 603 in 19.0 (ce7f5a3) and line 605 in 20.0 (688d86b)
  • docs.gocardless.com/docs/api-reference/webhooks
  • Module source code: flexigo_payment_gocardless: i18n/flexigo_payment_gocardless.pot, line 521 in 17.0 (d203ee6), line 521 in 18.0 (e2ab18b), line 521 in 19.0 (ce7f5a3) and line 523 in 20.0 (688d86b)
  • github.com/odoo/odoo/blob/20.0/addons/payment/i18n/payment.pot
  • github.com/odoo/odoo/blob/20.0/addons/payment/views/payment_transaction_views.xml
  • github.com/odoo/odoo/blob/17.0/odoo/http.py
  • github.com/odoo/odoo/blob/20.0/addons/account_payment/views/account_move_views.xml
  • Module source code: flexigo_payment_gocardless: __manifest__.py, line 31 in 17.0 (d203ee6), 18.0 (e2ab18b), 19.0 (ce7f5a3) and 20.0 (688d86b)