Automated rate-limit handling

When your hook calls an external API and receives HTTP 429 ("Too Many Requests"), Cloudhooks can wait and retry automatically instead of failing the run.

Short waits are handled instantly inside the same run. Longer waits re-schedule the entire hook to run again later. The run stays visible in Logs with a "Scheduled retry" status until it completes. This is an opt-in feature for paid plans. Free-plan hooks keep today's behavior: a 429 surfaces as an error.

Why this exists

Hooks that call rate-limited APIs would previously hit 429s and drop events. The only workaround was sleeping inside hook code, which isn't allowed (it ties up a worker and risks timeouts). This feature is the built-in, correct answer.

Important: This is per-run recovery. It rescues individual runs that hit 429s. It is not a proactive throttle that keeps you under an API's rate limit.

Plan availability

  • Starter (Free): Not included. 429s behave as today.
  • Trial: Included. 1,200 action limit applies; retries consume actions.
  • Pro: Included. Unlimited actions.
  • Custom: Included. Unlimited actions.

Non-entitled accounts still see the Rate-limit handling card in the hook's Settings tab, but with an upgrade prompt instead of the controls.

How to enable it

Open a hook → Settings tab → Rate-limit handling card.

Two checkboxes control the feature:

  • Checkbox 1: "Automatically handle rate limits (HTTP 429)"
    • On: Cloudhooks intercepts 429 responses from actions.http.* calls and handles them.
    • Off (default): A 429 returns to your hook code unchanged.
  • Checkbox 2: "My hook is safe to re-run (idempotent)"
    Only appears when checkbox 1 is on.
    • You're asserting that re-running the hook from the top won't cause duplicates.
    • Turning checkbox 1 off automatically clears this one.

Both settings are per-hook, not account-wide.

How it works

What triggers it

  • Only outbound HTTP actions: actions.http.get, .post, .put, .delete, .patch.
  • Only HTTP 429. Status 503 and all others surface unchanged. Network errors and other 4xx/5xx behave as before.

How long to wait

When a 429 comes back, Cloudhooks reads the Retry-After response header:

  • Seconds format: e.g., Retry-After: 30
  • HTTP date format: e.g., Retry-After: Wed, 21 Oct 2026 07:28:00 GMT (measured against the response's Date header to handle clock differences)

If the header is missing or unreadable, Cloudhooks uses exponential backoff: 15s → 60s → 240s → 960s → 3,840s (base 15s × 4 per attempt). A small jitter (+up to 10%) is added so multiple hooks don't retry simultaneously. Jitter is additive only, so a retry never fires before the server's deadline. A wait of 0 is floored to 1 second.

Short waits vs. long waits

The wait routes one of two ways, based on a 5-second threshold:

Short wait (≤5s): Handled in place. Cloudhooks waits and re-issues the same HTTP call inside the same run. Your code sees the eventual success or final error. Up to 5 in-place attempts. If there isn't enough time left in the run to wait safely, it falls through to the long-wait path.

Long wait (>5s): The whole hook is re-scheduled. Cloudhooks stops the current run and queues the entire hook to run again after the wait. The run shows as "Scheduled retry" in Logs until it wakes.

Re-scheduling runs the whole hook

A re-scheduled retry re-runs the entire hook from the top. There is no "resume from where it failed." Every action that ran before the failing call will run again on the next attempt. This is why the idempotency opt-in exists.

Behavior in the Tester ("Run a test")

A test run is synchronous and interactive—it can't be parked for minutes or hours. Rate-limit handling behaves slightly differently in the Tester:

  • Short waits are still handled in place, exactly like a live run, and the retry messages appear in the test output—you can watch a 429 recover.
  • Long waits are NOT re-scheduled. Instead of deferring the run, the Tester surfaces the 429 to your hook unchanged, with the message:

    Rate limit (429) from {host}: test runs don't defer or re-enqueue — surfacing it to your hook.

A hook that would show "Scheduled retry" in production will surface the 429 as an error in the Tester. That's expected—the same hook run for real would reschedule (or fail per the idempotency rules); the Tester simply can't wait that long.

The idempotency opt-in

This is the most important concept to understand.

The problem

A re-scheduled retry replays the whole hook, so any side effects earlier in the run (creating an order, sending an email, charging a card, writing a row) happen again on each replay. Also, a 429 does not prove the failed request didn't go through. The server may have processed your write and then rate-limited the response.

The rule Cloudhooks enforces

  • GET, PUT, DELETE: Always retried without needing the opt-in. Repeating them is safe by definition.
  • POST, PATCH: Retried only if you tick "My hook is safe to re-run (idempotent)." Without that, Cloudhooks will not silently replay a write. The run fails (clearly marked Failed) instead of risking duplicates.

What the opt-in means

You're promising that re-running the whole hook won't create duplicates, typically because the hook or destination API uses idempotency keys, upserts, or dedupe logic. Cloudhooks cannot verify this or checkpoint your hook. It's an assertion, and you own the consequences.

How to make a hook safe to re-run

  • Use the destination API's idempotency-key support.
  • Prefer PUT/upsert semantics over blind POST.
  • Design the hook so replaying it is harmless (server-side deduplication, conditional writes, etc.).

What you see in Logs

A re-scheduled run doesn't disappear. It appears in the run list with a "Scheduled retry" status:

  • A clock icon on the run row. Tooltip: "Retry at {date & time} · attempt {N}" while sleeping, or "Retrying now · attempt {N}" when due.
  • A "Scheduled retry" filter on the Logs tab finds these runs. Empty state: "No scheduled retries found."
  • Wake time shows as an absolute timestamp (no live countdown).
  • While scheduled, the run's Replay/Debug controls are hidden. Replaying manually would duplicate work.
Logs list with a Scheduled retry and clock icon tooltip

Event timeline

The run's event timeline is preserved across every attempt. Each retry appends a visible line, and every pass's events accumulate on the same run record. The final log is a full interleaved history.

Run detail timeline showing retry messages between passes

Log messages you may see

Log message Outcome Failure email?
Rate-limited (429) by {host} — waiting {N}s and retrying (attempt {N} of 5). Run continues (in-place retry) No
Rate-limited (429) by {host}. Retrying the hook in {N}s (attempt {N}). Scheduled, then continues No
Gave up after {N} rate-limit retries. The destination kept rate-limiting. Failed Yes
Rate limit (429) from {host}: the server-directed delay ({N}s) exceeds the supported maximum. The run was not retried. Failed Yes
Rate limit (429) from {host}: the run was not retried because the hook is not marked 'safe to re-run (idempotent)'. Failed Yes
Retry stopped: your plan no longer includes rate-limit handling after this run was queued for retry. Failed No
Retry stopped: automated rate-limit handling was turned off after this run was queued for retry. Failed No
Retry stopped: the safe-to-re-run opt-in was turned off after this run was queued for retry. Failed No
Retry stopped: the hook was edited after this run was queued for retry. Failed No

When you get a failure email

Failed-hook email notifications (a separate paid feature, off by default for entitled hooks) fire for genuine in-run 429 failures: a wait that exceeds the supported maximum, a POST/PATCH 429 on a hook not marked safe to re-run, or a "gave up after N retries" exhaustion. These represent runs where the hook executed and Cloudhooks did everything it could before finalizing as Failed.

The four "retry stopped" cases do not trigger failure emails. Those runs were stopped before executing (the retry was cancelled at wake because the plan, feature, opt-in, or hook code changed). Cloudhooks treats these the same way it treats an "action limit reached" block: no email, discover it on the Logs tab.

Billing

Retries consume actions. A re-scheduled retry re-runs the whole hook, so every action the hook executes on each pass counts toward your monthly usage and is billed.

  • Pro/Custom plans: Usage consideration only (unlimited actions).
  • Trial: Heavy retrying can hit the 1,200 action cap. If a scheduled retry would push the account over its limit, that retry is stopped with an upgrade prompt.
  • Short-wait in-place retry: Re-issuing the same call within one action counts as one action, not several.

Limits and safety nets

  • Maximum single wait: 24 hours. Also never scheduled past the run's log-retention window. If a server asks for longer, the run fails cleanly.
  • Maximum reschedules per run: 5. After that, Cloudhooks gives up and the run is marked Failed.
  • Re-checks when a retry wakes: Cloudhooks verifies the plan still includes the feature, the feature is still on, the idempotency opt-in (if needed) is still on, the hook exists and is enabled, and the hook code hasn't changed. If any changed, the run fails with a clear message.
  • Editing a hook mid-retry: Drops the in-flight retry safely. The new code won't replay under an idempotency promise made about the old code.
  • Fairness: Scheduled retries don't jump ahead of fresh events. Due retries are served behind work already queued; overdue retries eventually win.

What this feature is not

  • Not a proactive throttle. It reacts to 429s per run. It does not pace requests to keep you under an API's limit.
  • Not for 503 or other statuses. Only HTTP 429 is handled.
  • Not resume-from-failure. A reschedule replays the whole hook from the top.
  • Not on the free plan. Free hooks keep today's behavior.

FAQ

Will my writes be duplicated?
Only if you tick "safe to re-run" and your hook/API isn't actually idempotent. GET/PUT/DELETE are always safe; POST/PATCH only replay when you opt in.

Do retries cost me actions?
Yes. Each replay re-runs the hook and each action counts.

Why did my run say "Scheduled retry"?
It hit a 429 and is waiting to retry. It will finish on its own.

Why can't I replay a Scheduled retry manually?
It's already queued to retry. A manual replay would duplicate the work.

My hook uses 503/timeouts. Why isn't it retried?
Only 429 is handled.

I turned it on but nothing changed.
The API may not be returning 429, or your calls aren't using actions.http.*.

Why did my test run show the 429 as an error instead of "Scheduled retry"?
Test runs are synchronous and can't wait minutes or hours, so the Tester surfaces long-wait 429s to your hook instead of rescheduling. A real run would reschedule (or fail per the idempotency rules).

Current defaults

Behavior Default
Short/long threshold 5 seconds
In-place short retries 5 attempts
No-Retry-After backoff 15s → 60s → 240s → 960s → 3,840s (base 15s × 4 per attempt)
Jitter +up to 10% (additive only)
Minimum wait 1 second
Maximum single wait 24 hours (also capped by remaining log retention)
Maximum reschedules per run 5

These are operator-configurable and presented as current behavior, not guarantees.