Retries and timeouts
The SDKs retry the requests that are safe to repeat and leave the rest to you. A read that hits a 5xx is retried with backoff; a send that drops mid-flight is not, because the message may already have gone out.
What the SDKs retry#
| Request | Retried on | Why |
|---|---|---|
Every GET (conversations, messages, accounts, audit, stats, keys, endpoints, deliveries) | 429, 5xx, a timeout, a network error | Reads are idempotent; repeating one cannot change anything. |
webhooks.test, deliveries.replay, deliveries.replayAll | 429 only | A 429 means the request was never processed, so re-sending is safe. |
messages.reply, messages.sendAsHumanAgent, create, update, delete, rotate, revoke, realtime ticket | Never | A 5xx or a dropped connection may have gone through. Repeating a send could message someone twice. |
The default budget is two retries, so a read makes at most three attempts. The wait between attempts is a jittered exponential backoff (a 250 ms base in TypeScript and Go, 500 ms in Python, capped at a few seconds), and a Retry-After header on the response is honoured instead, capped at 60 seconds.
Set a timeout and a retry budget#
Every attempt runs under its own deadline, 30 seconds by default, covering the connection, the headers and the whole body. Both the deadline and the retry budget are client options.
import { HookChat } from '@hookchat/node'
const client = new HookChat({
apiKey: process.env.HOOKCHAT_API_KEY!,
baseUrl: process.env.HOOKCHAT_BASE_URL!,
timeoutMs: 5_000, // per attempt, default 30 000
maxRetries: 1, // default 2; 0 disables retries
})
const { name, version } = await client.ping()
console.log('ok', name, version)Per request#
A dashboard read can afford a short deadline and no retries where a nightly export cannot. Override the options on one call without building a second client.
import { HookChat } from '@hookchat/node'
const client = new HookChat({ apiKey: process.env.HOOKCHAT_API_KEY!, baseUrl: process.env.HOOKCHAT_BASE_URL! })
// Every method takes an optional trailing RequestOptions: { signal, timeoutMs, maxRetries }.
const controller = new AbortController()
const stats = await client.stats.get({}, { signal: controller.signal, timeoutMs: 5_000, maxRetries: 0 })
console.log('messages_24h', stats.messages_24h, 'dlq_total', stats.dlq_total)A timeout surfaces as HookChatError with code timeout in TypeScript, APITimeoutError in Python and a wrapped context.DeadlineExceeded in Go, each raised after the retry budget is spent. Cancelling with an AbortSignal or a cancelled context is never retried.
Sends are never retried for you#
message.sent event; a message.failed event means it did not go out. A send_failed (502) response is different: policy allowed the send and Meta refused it, so nothing was delivered and a retry can help.A 409 on a send (window_closed, human_agent_unavailable, missing_actor, rate_limited) is a state refusal, not a transient failure. Retrying it without changing anything returns the same answer. For rate_limited, wait for the budget to refill; the limits are on the troubleshooting page.
The gateway's own retries#
Client retries cover requests you make to HookChat. Deliveries HookChat makes to your endpoint have their own fixed schedule, eight attempts over about 33 hours, and a dead-letter queue; see Inspect and replay deliveries.