---
title: "Account events"
description: "Webhooks for keyword and wallet state changes and attention items, with no mention behind them."
canonical: https://docs.mentio.dev/webhooks/account-events
markdown: https://docs.mentio.dev/webhooks/account-events.mdx
---

# Account events

Webhooks for keyword and wallet state changes and attention items, with no mention behind them.

Account events report state changes, not posts: a keyword hitting its cap, the wallet pausing keywords, a spike that needs a look. Use them to let your own systems react.

## Subscribe a channel [#subscribe-a-channel]

No rule produces these events. A webhook channel subscribes to them by name, with `events` on the channel itself:

```bash
# Subscribe an endpoint to every account event
curl -sS -X PATCH "https://api.mentio.dev/v1/channels/dest_..." \
  -H "Authorization: Bearer mk_live_..." -H "Content-Type: application/json" \
  -d '{"events": ["keyword.capped", "keyword.paused_for_balance", "keyword.resumed", "wallet.low", "wallet.paused", "wallet.resumed"]}'
```

* `events` is also accepted on `POST /v1/channels` for a new webhook.
* On `PATCH` it replaces the whole set.
* It reads back on every channel as `config.events`, empty when the channel receives none.
* A channel subscribed after an event happened does not receive it.
* The endpoint keeps receiving whatever rules send it, unchanged.

Slack, email and Telegram channels can subscribe to the [attention events](#attention-events) only; any other name is a 400. They receive one short message per event instead of the JSON payload.

## Read the payload [#read-the-payload]

The envelope is the one every delivery uses. `alert` is `null`, since no rule produced it, and `data` is one of the shapes below. Verify it like any delivery ([Signature verification](/webhooks#signature-verification)).

```json
{
  "id": "dlv_7c2e...",
  "event": "keyword.capped",
  "createdAt": "2026-09-24T12:00:00.000Z",
  "alert": null,
  "data": {
    "keyword": { "id": "kw_60d9...", "term": "bloom", "kind": "brand" },
    "cap": { "mentions": 1000, "per": "month" },
    "thisMonth": 1000,
    "pausedAt": "2026-09-24T12:00:00.000Z",
    "resumesAt": "2026-10-01T00:00:00.000Z"
  }
}
```

## The events [#the-events]

| Event                        | When                                                                                                         | `data`                                                                                                                                                                                                                                       |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `keyword.capped`             | A keyword reached its mention cap and stopped matching                                                       | `keyword`, `cap.mentions`, `cap.per` (`day`, `week` or `month`), `thisMonth` (matches recorded in the cap's period the pause fell in), `pausedAt`, `resumesAt` (when the next period starts, UTC, unless the cap is raised or removed first) |
| `keyword.paused_for_balance` | The wallet ran out and paused this keyword                                                                   | `keyword`, `pausedAt`, `wallet`                                                                                                                                                                                                              |
| `keyword.resumed`            | The keyword listens again                                                                                    | `keyword`, `reason`, `resumedAt`                                                                                                                                                                                                             |
| `wallet.low`                 | The effective balance fell to 20% of the last credit. Once per credit: a top-up opens a new epoch            | `wallet`, `lastCreditCents`, `at`                                                                                                                                                                                                            |
| `wallet.paused`              | The wallet paused every keyword. Each keyword also gets its own `keyword.paused_for_balance`                 | `wallet`, `keywordsPaused`, `at`                                                                                                                                                                                                             |
| `wallet.resumed`             | A credit covered a day of every keyword and they came back. Each keyword also gets its own `keyword.resumed` | `wallet`, `keywordsResumed`, `at`                                                                                                                                                                                                            |
| `mention.spike`              | A keyword got far more fresh mentions in the last full hour than it usually does                             | `attentionId`, `url`, `keyword`, `window`, `matches`, `relevant`, `baseline` (`meanPerHour`, `stddevPerHour`, `hours`)                                                                                                                       |
| `sentiment.negative_spike`   | A keyword's negative share of the last 24 hours jumped against its week before                               | `attentionId`, `url`, `keyword`, `window`, `negative`, `relevant`, `share`, `baseline` (`negative`, `relevant`, `share`)                                                                                                                     |
| `keyword.noisy`              | Most of a keyword's scored matches are noise (its health reads `noisy`)                                      | `attentionId`, `url`, `keyword`, `windowDays`, `scored`, `relevant`, `noiseShare`                                                                                                                                                            |
| `channel.failing`            | A channel's last 5 sends of the last 24 hours all failed                                                     | `attentionId`, `url`, `channel` (`id`, `kind`, `label`), `failures`, `lastError`, `since`                                                                                                                                                    |
| `feed.failing`               | A [workspace feed](/platforms/rss#workspace-feeds) failed its last 3 hourly reads                            | `attentionId`, `url`, `feed` (`id`, `url`, `title`), `reason`, `detail`, `failingSince`, `keywordsAffected`                                                                                                                                  |
| `feed.recovered`             | That feed read well again. Once per `feed.failing`                                                           | the same, plus `recoveredAt`                                                                                                                                                                                                                 |

### Resume reasons [#resume-reasons]

`reason` on `keyword.resumed` is one of:

| `reason`           | Meaning                                                |
| ------------------ | ------------------------------------------------------ |
| `balance_restored` | A credit covered a day of every keyword                |
| `cap_raised`       | A `PATCH` set the cap above the current period's count |
| `cap_removed`      | A `PATCH` removed the cap                              |
| `month_turned`     | The first of the month, UTC, for a monthly cap         |
| `period_turned`    | The next day or Monday, UTC, for a daily or weekly cap |

A manual pause or unpause of a keyword sends nothing: that is your own action, and the keyword's `muted` says so.

### The wallet object [#the-wallet-object]

`wallet` is the wallet as the event saw it, after the change. [`GET /v1/usage`](/api/usage/get-usage) has the live numbers.

| Field                              | Meaning                                                                                   |
| ---------------------------------- | ----------------------------------------------------------------------------------------- |
| `balanceCents`                     | The balance                                                                               |
| `effectiveCents`                   | What the stop rule read                                                                   |
| `nextDayCents`                     | One more day of the running keywords                                                      |
| `resumeCostCents`                  | One day of every keyword, running and paused: the balance that resumes a paused workspace |
| `activeKeywords`, `pausedKeywords` | How many keywords run and how many are paused                                             |

### Feed failure reasons [#feed-failure-reasons]

`reason` on the feed events is one of:

* `login_required`: a sign-in or access-denied page. Give the feed a [sign-in](/platforms/rss#members-only-forums).
* `login_expired`: the same, on a feed that has one. Copy a fresh cookie.
* `blocked`, `not_found`, `not_a_feed` or `unreachable`.

### Attention events [#attention-events]

The last six events are **attention events**, the only ones a Slack, email or Telegram channel can subscribe to. When and how they open is in [Needs attention](/guides/attention).

* The first four are each sent once, when an attention item opens.
* `feed.failing` opens an item too, and `feed.recovered` resolves it. A feed that fails and comes back is two messages.
* `attentionId` is the item; `GET /v1/attention` lists them.
* Their `keyword` also carries `name` (the term, or "term (Group)" outside the default group) and `groupId`.

## Handle delivery [#handle-delivery]

An event is recorded the moment the transition happens and reaches your endpoint within about a minute.

* Every event is delivered at least once.
* Retries follow the [same rules as mention deliveries](/webhooks#delivery-retries): five attempts, the same `id` each time, so dedupe on it.
* The channel's delivery log ([`GET /v1/channels/{id}/deliveries`](/api/channels/list-channel-deliveries)) lists them with `kind: "event"` and the event name.

The emails to workspace owners (low balance, tracking paused, a keyword at its cap) still go out as before. The webhooks are for the systems that act on them.

## Related [#related]

* [Webhooks](/webhooks): setup, retries and signature verification.
* [Update a channel](/api/channels/update-channel): set `events` on a channel.
* [Needs attention](/guides/attention): when attention items open and resolve.
* [Get usage](/api/usage/get-usage): the live wallet and keyword numbers.
