---
title: "Account events"
description: "Webhooks for keyword and wallet state changes, 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, with no mention behind them.

A keyword hitting its monthly cap, the wallet pausing every keyword for lack of balance, a keyword listening again: these are state changes, not posts, so no rule produces them. A webhook channel subscribes to them directly, 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, replaces the whole set on `PATCH`, and reads back on every webhook channel (`config.events`, empty when it receives none). A channel subscribed after an event happened does not receive it. The same endpoint keeps receiving whatever rules send it, unchanged.

## 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. Sign-verify it exactly like a mention 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 },
    "thisMonth": 1000,
    "pausedAt": "2026-09-24T12:00:00.000Z",
    "resumesAt": "2026-10-01T00:00:00.000Z"
  }
}
```

## The events

| Event                        | When                                                                                                         | `data`                                                                                                                                                                                      |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `keyword.capped`             | A keyword reached its monthly mention cap and stopped matching                                               | `keyword`, `cap.mentions`, `thisMonth` (matches recorded in the month the pause fell in), `pausedAt`, `resumesAt` (the first of next month, 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`                                                                                                                                                           |

`reason` on `keyword.resumed` is one of `balance_restored` (a credit covered a day of every keyword), `cap_raised` (a `PATCH` set the cap above this month's count), `cap_removed` (a `PATCH` removed the cap) or `month_turned` (the first of the month, UTC).

`wallet` is the wallet as the event saw it, after the change: `balanceCents`, `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` and `pausedKeywords`. [`GET /v1/usage`](/api/usage/get-usage) has the live numbers.

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

## 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.

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