---
title: "Account events"
description: "Webhooks for changes to keywords, your balance and things that need attention, which are not tied to a mention."
canonical: https://docs.rumoro.dev/webhooks/account-events
markdown: https://docs.rumoro.dev/webhooks/account-events.mdx
---

# Account events

Webhooks for changes to keywords, your balance and things that need attention, which are not tied to a mention.

Some things that happen aren't posts. A keyword reaches its cap, the balance runs out, a keyword starts collecting again, or a spike needs a look. No alert rule sends these. Instead, a channel subscribes to them by name with `events`.

```ts tab="TypeScript"
await rumoro.updateChannel({
  path: { id: 'dest_...' },
  body: { events: ['keyword.capped', 'keyword.paused_for_balance', 'keyword.resumed', 'wallet.low', 'wallet.paused', 'wallet.resumed'] },
});
```

```python tab="Python"
rumoro.channels.update(
    "dest_...",
    events=["keyword.capped", "keyword.paused_for_balance", "keyword.resumed", "wallet.low", "wallet.paused", "wallet.resumed"],
)
```

```bash tab="curl"
curl -X PATCH https://api.rumoro.dev/v1/channels/dest_... \
  -H "Authorization: Bearer $RUMORO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "events": ["keyword.capped", "keyword.paused_for_balance", "keyword.resumed", "wallet.low", "wallet.paused", "wallet.resumed"] }'
```

You can also send `events` on `POST /v1/channels`. On `PATCH` it replaces the whole list. The channel shows its list as `config.events`, which is empty when it subscribes to none. A channel only gets events that happen after it subscribes. Its rules keep working as before.

Slack, email and Telegram channels can subscribe to the four attention events only. Other names return `400`. Instead of JSON, they get a one-line "Needs attention" message with an **Open in Rumoro** link.

## The payload

Account events use the normal envelope with `alert: null`. Check the signature like for any delivery ([signature verification](/webhooks#signature-verification)).

```json
{
  "id": "dlv_...",
  "event": "keyword.capped",
  "createdAt": "2026-10-04T15:20:00.000Z",
  "alert": null,
  "data": {
    "keyword": { "id": "kw_...", "term": "driftwood", "kind": "brand" },
    "cap": { "mentions": 750 },
    "thisMonth": 750,
    "pausedAt": "2026-10-04T15:20:00.000Z",
    "resumesAt": "2026-11-01T00:00:00.000Z"
  }
}
```

## The events

| Event | Sent when | `data` |
| --- | --- | --- |
| `keyword.capped` | A keyword reached its monthly cap | `keyword`, `cap.mentions`, `thisMonth` (matches in the month of the pause), `pausedAt`, `resumesAt` (the first of next month in UTC, unless you raise or remove the cap before) |
| `keyword.paused_for_balance` | This keyword stopped because the balance ran out | `keyword`, `pausedAt`, `wallet` |
| `keyword.resumed` | The keyword is collecting again | `keyword`, `reason`, `resumedAt` |
| `wallet.low` | The effective balance dropped to 20% of the last credit. Sent once per credit, and not while tracking is paused. | `wallet`, `lastCreditCents`, `at` |
| `wallet.paused` | All keywords stopped for lack of balance | `wallet`, `keywordsPaused`, `at` |
| `wallet.resumed` | A credit covered one day of all keywords | `wallet`, `keywordsResumed`, `at` |
| `mention.spike` | A keyword got far more mentions in the last full hour than usual | `attentionId`, `url`, `keyword`, `window`, `matches`, `relevant`, `baseline` (`meanPerHour`, `stddevPerHour`, `hours`) |
| `sentiment.negative_spike` | A keyword's share of negative mentions over 24 hours jumped compared with the week before | `attentionId`, `url`, `keyword`, `window`, `negative`, `relevant`, `share`, `baseline` (`negative`, `relevant`, `share`) |
| `keyword.noisy` | Most of a keyword's scored matches are noise | `attentionId`, `url`, `keyword`, `windowDays`, `scored`, `relevant`, `noiseShare` |
| `channel.failing` | A channel's last 5 deliveries in 24 hours all failed | `attentionId`, `url`, `channel` (`id`, `kind`, `label`), `failures`, `lastError`, `since` |

When the whole wallet pauses or restarts, you first get one `keyword.paused_for_balance` or `keyword.resumed` per keyword, then `wallet.paused` or `wallet.resumed`.

`reason` on `keyword.resumed` tells you why.

| `reason` | Meaning |
| --- | --- |
| `balance_restored` | A credit brought the balance back |
| `cap_raised` | The cap was raised, including when the first top-up lifts the welcome cap |
| `cap_removed` | The cap was removed |
| `month_turned` | A new month started |

`wallet` is the balance right after the change, with `balanceCents`, `effectiveCents`, `nextDayCents` (one more day of the running keywords), `resumeCostCents` (one day of all keywords), `activeKeywords` and `pausedKeywords`. [`GET /v1/usage`](/api/usage/get-usage) has the current numbers.

Muting or unmuting a keyword yourself sends no event.

**Attention events** (the last four) go out once, as an attention item opens. `attentionId` is that item, and `GET /v1/attention` lists all of them. Their `keyword` also has `name`, which is the term, or "term (Group)" for a keyword outside the default group, and `groupId`. `url` opens the matching page in the dashboard. See [Needs attention](/guides/attention).

## Delivery

An event is recorded at the moment of the change, and the delivery run that follows, every minute, sends it. You get each event at least once, with the [usual retries](/webhooks#retries) and the same `id` on every attempt. In the [delivery log](/api/alerts/list-channel-deliveries) they have `kind: "event"` and the event name. Owners still get their emails about low balance, paused tracking and capped keywords.
