Skip to content
4 changes: 2 additions & 2 deletions advanced/websocket-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1047,7 +1047,7 @@ Each stream error sets `is_logged_in = false` and fires the `shutdown_notifier`
|------|--------|-------|-------------|
| **401** | Disables auto-reconnect | `LoggedOut` | No — session invalid, must re-pair |
| **409** | Disables auto-reconnect | `StreamReplaced` | No — prevents displacement loop |
| **429** | Adds 5 to backoff counter | None | Yes — extended Fibonacci backoff |
| **429** | Adds 5 to backoff counter | `StreamError` | Yes — extended Fibonacci backoff |
| **503** | Normal handling | None | Yes — standard backoff |
| **515** | Marks as expected disconnect | None | Yes — immediate, no backoff |
| **516** | Disables auto-reconnect | `LoggedOut` | No — device removed |
Expand Down Expand Up @@ -1119,7 +1119,7 @@ Maximum: 900s (15 minutes)
Jitter: ±10%
```

For rate-limited errors (429), the backoff counter is incremented by 5 before the normal increment, causing the delay to jump significantly on the next reconnection attempt.
For rate-limited errors (429), the client increments the backoff counter by 5 before the normal increment, causing the delay to jump significantly on the next reconnection attempt. Since [#1263](https://github.com/oxidezap/whatsapp-rust/pull/1263), the client also dispatches `Event::StreamError` for 429 — WA Web gives its UI no signal here at all (429 is outside the `500..600` range its handler special-cases), but an embedder has no UI to fall back on, so the rate limit is reported like every other coded stream error.

**Stability-gated reset.** The backoff counter does not reset to its base immediately on a successful `<success>` authentication. Instead, `connected_at_ms` records the auth time, and the counter only resets when the *next* disconnect finds the connection was stable for at least `STABLE_CONNECTION_RESET_MS` (30s) — matching WA Web's `resetDelay`. A connection that authenticates and then immediately drops keeps escalating the backoff instead of resetting to 1s and retrying in a tight loop.

Expand Down
4 changes: 3 additions & 1 deletion api/client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ The client handles specific `<stream:error>` codes from the WhatsApp server:
|-------------------|---------|---------------|----------------|
| **401** | Session invalidated (unauthorized) | `LoggedOut` | Disabled — must re-pair |
| **409** | Another client connected (conflict) | `StreamReplaced` | Disabled — prevents displacement loop |
| **429** | Rate limited (too many connections) | None (emits `Disconnected`) | Yes, with extended backoff (+5 steps) |
| **429** | Rate limited (too many connections) | `StreamError`, then `Disconnected` | Yes, with extended backoff (+5 steps) |
| **503** | Service unavailable | None | Yes, normal backoff |
| **515** | Expected disconnect (e.g., post-pairing) | None | Yes, immediate (no backoff) |
| **516** | Device removed | `LoggedOut` | Disabled — must re-pair |
Expand All @@ -509,6 +509,8 @@ When you receive a `LoggedOut` or `StreamReplaced` event, auto-reconnect is perm

When the server returns a 429 stream error, the client bumps the internal backoff counter by 5 Fibonacci steps before reconnecting. This means the reconnection delay jumps significantly (e.g., from ~1s to ~13s on the first rate limit) to respect the server's throttling.

As of [#1263](https://github.com/oxidezap/whatsapp-rust/pull/1263), the client also dispatches an `Event::StreamError` for 429 (code `"429"`). WhatsApp Web's own handler gives no UI signal for this case — it only special-cases `500..600`. An embedder has no UI to fall back on, so 429 is now reported the same way every other coded stream error is. This event fires in addition to `Event::Disconnected`, not instead of it. The 429 handler never marks the disconnect as expected, so the shared connection-loss path still dispatches `Disconnected` once the socket closes, the same as every other unexpected drop.

### General reconnection behavior

| Scenario | Behavior |
Expand Down
8 changes: 4 additions & 4 deletions concepts/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ Event::LoggedOut(logout) => {

### StreamError

**Emitted:** For unrecognized stream error codes (codes not matching 401, 409, 429, 503, 515, or 516)
**Emitted:** For unrecognized stream error codes (codes not matching 401, 409, 429, 503, 515, or 516), and — since [#1263](https://github.com/oxidezap/whatsapp-rust/pull/1263) — also for `429` (rate-limited), even though 429 is itself a recognized, explicitly-handled code. WhatsApp Web's own handler has no arm for it either (only `500..600` is special-cased there), so reporting 429 here is an embedder-facing choice rather than a fidelity fix.

```rust
#[derive(Debug, Clone, Serialize, bon::Builder)]
Expand All @@ -477,15 +477,15 @@ pub struct StreamError {
**Usage:**
```rust
Event::StreamError(err) => {
eprintln!("Unknown stream error: {} (raw: {:?})", err.code, err.raw);
eprintln!("Stream error: {} (raw: {:?})", err.code, err.raw);
}
```

<Note>
Recognized stream error codes emit specific events instead of `StreamError`:
Specific stream error codes have the following event behavior:
- **401** → `LoggedOut` (session invalidated)
- **409** → `StreamReplaced` (another client connected)
- **429** → No event emitted (reconnects with extended backoff)
- **429** → `StreamError` (rate limited; also reconnects with extended backoff)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the example that labels every StreamError unknown

Once 429 reaches this event, the unchanged usage example above prints Unknown stream error for a known rate-limit condition. Consumers who copy the example will therefore misclassify 429s in their logs; use a neutral label or branch on err.code before describing the event as unknown.

Useful? React with 👍 / 👎.

- **503** → No event emitted (reconnects with normal backoff)
- **515** → No event emitted (immediate reconnect, e.g., after pairing)
- **516** → `LoggedOut` (device removed)
Expand Down