---
seo:
  description: >-
    Build your own integrations on top of a TipPage: read tip history, watch and
    control the TTS queue and media queue, and receive signed webhooks the
    moment…
sidebar:
  label: Overview
title: TipPage Developer API
---
Build your own integrations on top of a TipPage: read tip history, watch and
control the **TTS queue** and **media queue**, and receive **signed webhooks**
the moment things happen. Every tip produces one TTS entry and, optionally,
one media entry.

> **Changed 2026-09-06 - viewers are platform-neutral.** A viewer can sign
> in to the tip page with Twitch or Kick and link the other account, so
> every tip, viewer and credit object that carried `twitch_user_id` now
> carries `platform` (`twitch` | `kick`) and `platform_user_id` instead -
> there is no alias. The viewer object gained `linked_accounts`, and the
> `{user}` path segment accepts `<platform>:<id>` (`kick:123`) alongside a
> bare id or a name.

## Download the spec

This entire reference is generated from one OpenAPI 3.1 document:
**[openapi.yaml](https://docs.tippage.com/openapi.yaml)**. Import it into
Postman, Insomnia, or Bruno, or feed it to a client generator.

## Authentication

Every request needs an API key sent as a bearer token:

```
Authorization: Bearer tp_live_...
```

Keys are created by the streamer in **Dashboard → Settings → Developer**
(owner and super admins only). The key identifies the streamer, so there is
no account id anywhere in these paths. Each key carries **scopes** - the
operations below name the scope they need. Keys are server-side
credentials: never embed one in a browser page or show it on stream.

## Rate limits

240 requests/min per IP and 120 requests/min per key. Chat sends
(`POST /chat/messages`) have their own budget on top: 20 messages per
30 seconds per streamer, shared across all keys. Limit state is
returned in standard `RateLimit-*` headers; exceeding it returns `429`.

## Errors

Errors are JSON: `{ "error": "<human message>", "code": "<machine_code>" }`.

## Webhooks

Register endpoint URLs (here or in the dashboard) and TipPage POSTs a JSON
event envelope to them. See the **Webhooks** section at the bottom for
every event type and payload.

**Verify every delivery before trusting it.** Your endpoint is a public
URL - anyone who discovers it can POST a perfectly-shaped envelope. The
`TipPage-Signature` header is the **only** proof a request came from
TipPage; every other header and the envelope itself are plain text anyone
can forge, and source-IP allowlisting doesn't work (delivery IPs are not
stable). Each request carries:

| Header | Value |
|---|---|
| `TipPage-Signature` | `t=<unix seconds>,v1=<hex HMAC-SHA256>` - **the proof of origin** |
| `TipPage-Event` | the event type (e.g. `tip.created`) - informational |
| `TipPage-Event-Id` | the envelope id (`evt_...`) - informational |
| `TipPage-Delivery` | the delivery id (`wd_...`, or `ping` for tests) - informational |
| `TipPage-Attempt` | delivery attempt number (`1`-`8`); a value above 1 means this is a retry of the same event, byte-identical body - informational |

The MAC is HMAC-SHA256 over `<t>.<raw request body>` with your endpoint's
signing secret (`whsec_...`, shown once at creation). Verify against the
**raw body bytes** (before any JSON parser touches the request), reject
`t` more than ~5 minutes from your clock (blocks replays of captured
deliveries), and compare MACs in constant time:

```js
import crypto from "node:crypto";

function verify(secret, header, rawBody, toleranceSec = 300) {
  const parts = Object.fromEntries(
    (header || "").split(",").map((p) => p.split("="))
  );
  if (!parts.t || !parts.v1) return false;
  if (!(Math.abs(Date.now() / 1000 - Number(parts.t)) <= toleranceSec)) return false;
  const mac = crypto.createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`).digest("hex");
  const got = Buffer.from(parts.v1, "hex");
  const want = Buffer.from(mac, "hex");
  return got.length === want.length && crypto.timingSafeEqual(got, want);
}
```

On a failed check, respond `401` and do nothing else with the request.
Retries re-sign the byte-identical body with a fresh `t`, so verify every
request, not once per event id - and since delivery is at-least-once,
dedupe verified events by the envelope `id`. The full verification guide
(with an Express example and common pitfalls) is at
[docs.tippage.com/api](https://docs.tippage.com/api).

Delivery is **at-least-once** with no ordering guarantee. Non-2xx responses
retry with backoff for up to ~44 hours (8 attempts); an endpoint that keeps
failing is disabled automatically and the streamer is notified. Endpoint
URLs must be public `https://` - private and internal addresses are
rejected. Answer with any 2xx as fast as possible and do real work async.

<ApiOverview source="api-reference" />

## Identity

Key introspection and API discovery.

<ApiTagOperations source="api-reference" tag="identity" />

## Realtime

The webhook event stream, live over a WebSocket at wss://ws.tippage.com - same events, same payloads, same event ids as the webhooks, delivered while you're connected. No SDK needed: open a plain WebSocket, send one JSON line - `{"data": {"apiKey": "tp_live_..."}}` for connections you control, or `{"data": {"token": "..."}}` with a short-lived token from POST /realtime/token to let clients that shouldn't hold your key (browsers, third parties) connect. The server streams the events your key's scopes allow. Full guide, wire format, channel table, and lifecycle: https://docs.tippage.com/api/realtime

<ApiTagOperations source="api-reference" tag="realtime" />

## TTS queue

The pending TTS queue and its played history. Reads need `tts:read`; controls need `tts:control`. The start/finish pair is the same protocol the stream overlay speaks - an external consumer can claim a TTS, play it its own way, and finish it into history. Full pattern: [claim, do your thing, finish](https://docs.tippage.com/api/claim-and-finish).

<ApiTagOperations source="api-reference" tag="tts-queue" />

## Media queue

The media (video request) queue, its played history, live playback status, and direct video queueing. Reads need `media:read`; controls and queueing need `media:control`.

<ApiTagOperations source="api-reference" tag="media-queue" />

## Tipping

Tips themselves: create manual tips (`tips:create`), the accept-new-tips switch (status readable by any valid key, opening/closing via `tipping:control`), and the supporter leaderboard (any valid key).

<ApiTagOperations source="api-reference" tag="tipping" />

## AI voices

AI voice TTS (TipPage+): the curated voice catalog with per-voice enabled flags, this month's character usage, and toggles for the whole feature or individual voices. Needs `ai_voices:manage`. Every endpoint answers `404` unless AI voices are available to your account.

<ApiTagOperations source="api-reference" tag="ai-voices" />

## Viewers

Viewer management for the sub-rewards system. A viewer is someone who has signed in to the tip page - with Twitch or Kick - and every viewer object names that account as `platform` + `platform_user_id`, with the person's other linked account(s) in `linked_accounts`. List them with their first sign-in dates and credit balances (`viewers:read`), read one viewer's grant ledger and sub-reward usage, and grant or revoke credits (`viewers:manage`). Balances, grants and reward history are read across a person's linked accounts; a grant or revoke lands on the exact account you name. Everywhere a `{user}` appears in a path you can pass `<platform>:<id>` (`kick:123`), a bare numeric id, or the viewer's name (login or display name, case-insensitive, on either platform). Someone who has never signed in can't be looked up or granted credits - those requests answer `404 viewer_not_signed_in`.

<ApiTagOperations source="api-reference" tag="viewers" />

## Channel points

Resolve channel point redemptions on Twitch. Needs `channel_points:manage`. Only works for TipPage-managed rewards - Twitch restricts redemption resolution to the app that created the reward. Pair with the `twitch.channel_point_redemption` webhook, whose `is_managed` flag says whether a redemption is resolvable.

<ApiTagOperations source="api-reference" tag="channel-points" />

## Webhook endpoints

Manage where events get delivered. Needs `webhooks:manage`. Endpoints can also be managed in the dashboard.

<ApiTagOperations source="api-reference" tag="webhook-endpoints" />

## Chat timers

Scheduled chat-bot messages - the bot posts them on a fixed or randomized interval, optionally only while the streamer is live or after enough chat activity. Needs `timers:manage`. The same timers as the dashboard's Chat bot page; max 20 per streamer. Editing a timer's interval restarts its schedule from now.

<ApiTagOperations source="api-reference" tag="chat-timers" />

## Counters

Named per-streamer numbers the chat bot bumps with `{count}` / `{count:name}` command tokens and overlay Data labels can display live. Addressed by name - a lowercase slug (`a-z 0-9 _ -`, max 64 chars) is the key on every surface. Needs `counters:manage`. Every change made here behaves exactly like a chat bump: overlays update live and the `counter.updated` webhook fires. Max 100 per streamer.

<ApiTagOperations source="api-reference" tag="counters" />

## Chat bot

The streamer's chat bot: its status and send identity (any valid key), speaking in chat as the bot (`chat:write`), and the custom `!commands` it answers (`commands:manage`, addressed by command name - the trigger without the `!`). Pair with the `chat.command` webhook event to react when a custom command runs.

<ApiTagOperations source="api-reference" tag="chat-bot" />

## Overlays

The account's overlays: list them, inspect one (name, resolution, widget types - never overlay keys), delete one, reload a connected one, fire the Event celebration widget on demand (all of them or one specific instance), and read, update, or add to each overlay's tip goal. All of it needs `overlays:manage`.

<ApiTagOperations source="api-reference" tag="overlays" />
