1. Start here
  2. Send your first notification

​
WhooshBang quickstart

Go from an empty organization to a message on a real phone, then keep going into the API you will actually integrate against. Nothing here needs the dashboard, and nothing here needs a Telegram account until the last step.

​
The whole thing, in three commands

npm install @whooshbang/sdk
npx wb login

npx wb project create "Synthetic Alerts"
npx wb subscriber create me
npx wb send me "Hello from WhooshBang."

project create makes the project, its test and live environments, and connects and routes the shared WhooshBang Telegram channel — because a project with no way to send is not a thing anybody wanted. subscriber create prints a link; opening it on a handset and pressing Start is the consent. Then you can send.

Every command is documented in the command line reference. The rest of this page is the same journey through the API, for when you are putting it inside an application.

Every request and response below is checked against the published V1 contract by npm run docs:snippets, so nothing here can drift from the API without failing the build.

  • API base URL: https://api.whooshbang.com
  • SDK: @whooshbang/sdk (ESM, bring your own fetch)
  • Dashboard: https://whooshbang.com — optional; the command line reaches everything below.

Read the integration reference for idempotency rules, failure diagnosis, and what the delivery states actually promise.

WhooshBang is in invited alpha. Setting up, authorizing, and sending through a real connection requires invited-alpha access.

​
1. Create a project and a credential

npx wb project create "Synthetic Alerts"

That is the project, both environments, a Default notifier in each, and a routed Telegram connection in test. Add --no-connection if you would rather choose the channel yourself later.

An application does not authenticate as you, so it needs its own credential:

npx wb credential create --label "Production integration" \
  --scope messages:write --scope messages:read \
  --scope subscriptions:write --scope subscriptions:read

The complete secret is printed once. It goes to standard output and its warning to standard error, so wb credential create > secret.txt captures the value and still shows you the warning. Afterwards only the prefix, label, scopes, creator, creation time, last-used time and status are retrievable.

Give it the scopes this guide uses and nothing more:

{
  "label": "Production integration",
  "scopes": [
    "messages:write",
    "messages:read",
    "subscriptions:write",
    "subscriptions:read"
  ]
}

Store the secret the way you store any other production credential. It is not retrievable later — if you lose it, rotate.

Set up the client you will use for the rest of this guide:

export WHOOSHBANG_CREDENTIAL='<the secret wb credential create printed>'
import { WhooshBangClient } from "@whooshbang/sdk";

const whooshBang = new WhooshBangClient({
  baseUrl: "https://api.whooshbang.com",
  credential: process.env.WHOOSHBANG_CREDENTIAL!,
  fetch,
});

The credential fixes the organization, project, and environment. There is no environment parameter to get wrong, and no routing decision to make: a send reaches the subscriber’s authorized connection.

​
2. Choose the identity your users will see

project create connected the shared WhooshBang bot for you, which is the right answer when you are notifying yourself — from an agent, an automation, a cron job, a webhook.

When you want your name in the chat instead, that is a different channel connection, and Channels covers the three choices, what each one costs, and how to make it through the API.

​
3. Get a real subscriber authorized

Real sends need a Telegram user who has authorized your notifier. In the same environment as your credential, invite them under the subscriber id your application already uses — never a Telegram handle, which WhooshBang never asks for.

Check these in order; every resource must belong to the same project and environment fixed by the credential:

  1. Create the project and choose its test or live environment.
  2. Activate and route a channel connection for the requested channel. The CLI’s default project create does this for test; API-created projects may need the explicit channel setup.
  3. Use an active notifier. Every new project has an active Default notifier, so omit notifier_id unless you deliberately created another.
  4. During invited alpha, ask your WhooshBang contact to grant the organization in the deployment you are using. The grant is an operator action; no database write or customer impersonation is part of this journey.
  5. Create and present the link. Sending becomes possible only after the recipient opens it and consents, which creates the binding.

If the notifier cannot be resolved, the API returns a 404 with field_errors[0].code = "notifier_not_found". If no eligible connection can be selected it returns the same tenant-safe 404 with connection_unavailable and points at either /connection_id or /channels/0.

npx wb subscriber create customer_123

Through the API, that is a subscription link naming the connection and the subscriber:

{
  "subscriber_id": "customer_123",
  "channels": ["telegram"],
  "connection_id": "connection_synthetic_telegram_001",
  "recipient_language": "es",
  "return_url": "https://example.test/settings/notifications"
}
curl --silent --show-error \
  --request POST https://api.whooshbang.com/v1/subscription-links \
  --header "Authorization: Bearer $WHOOSHBANG_CREDENTIAL" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: quickstart-link-1' \
  --data '{
    "subscriber_id": "customer_123",
    "channels": ["telegram"],
    "connection_id": "connection_synthetic_telegram_001",
    "recipient_language": "es",
    "return_url": "https://example.test/settings/notifications"
  }'
const link = await whooshBang.createSubscriptionLink(
  {
    subscriber_id: "customer_123",
    channels: ["telegram"],
    connection_id: "connection_synthetic_telegram_001",
    recipient_language: "es",
    return_url: "https://example.test/settings/notifications",
  },
  { idempotencyKey: "quickstart-link-1" },
);

The response carries an authorization_url. Send your user there — from a settings page, an email, wherever you already talk to them. (The example below is from a test environment; a live link is identical apart from environment.)

{
  "id": "slink_synthetic_001",
  "project_id": "project_synthetic",
  "environment": "test",
  "subscriber_id": "customer_123",
  "notifier_id": "default",
  "channels": ["telegram"],
  "recipient_language": "es",
  "connection": {
    "id": "connection_synthetic_telegram_001",
    "mode": "whooshbang_shared",
    "display_name": "WhooshBang",
    "identity": { "handle": "WhooshBangSyntheticBot", "provider": "telegram" }
  },
  "status": "pending",
  "authorization_url": "https://t.me/WhooshBangSyntheticBot?start=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  "created_at": "2026-07-25T17:45:00Z",
  "updated_at": "2026-07-25T17:45:00Z",
  "expires_at": "2026-07-25T18:00:00Z",
  "diagnostic_id": "diag_link_pending_001"
}

Opening it starts the selected bot directly in Telegram. The bot shows who is asking, what authorization permits, and how to stop messages, then asks the user to confirm. You never see their Telegram username, user ID, or chat ID.

Poll the link — or just watch for status: "activated" — to know when the binding exists:

curl --silent --show-error \
  --header "Authorization: Bearer $WHOOSHBANG_CREDENTIAL" \
  "https://api.whooshbang.com/v1/subscription-links/$LINK_ID"
const activated = await whooshBang.getSubscriptionLink(link.id);
if (activated.status === "activated") {
  // activated.binding is the durable route; the link has done its job.
}

The link is single-use and expires. If it lapses before the user acts, issue a new one — that is the normal path, not an error condition.

​
4. Send your first Telegram notification

Once they have accepted, send to the same subscriber id:

npx wb send customer_123 "Your export is ready."

Or ask them something, and wait for the answer:

npx wb ask customer_123 "Deploy build 41 to production?" --wait 120

Through the API, select the connection transport:

curl --silent --show-error --include \
  --request POST https://api.whooshbang.com/v1/messages \
  --header "Authorization: Bearer $WHOOSHBANG_CREDENTIAL" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: quickstart-telegram-send-1' \
  --data '{
    "delivery_target": "connection",
    "to": { "subscriber_id": "customer_123" },
    "content": { "type": "text", "text": "Your export is ready." }
  }'
const telegramSend = await whooshBang.createMessage(
  {
    delivery_target: "connection",
    to: { subscriber_id: "customer_123" },
    content: { type: "text", text: "Your export is ready." },
  },
  { idempotencyKey: "quickstart-telegram-send-1" },
);
switch (telegramSend.outcome) {
  case "accepted":
    console.log(telegramSend.message.id);
    break;
  case "subscriber_unbound":
    // customer_123 has no active binding: they revoked it, or never finished
    // subscribing. Nothing was sent; send them a new subscription link.
    break;
  case "preferred_binding_unavailable":
    // The channel customer_123 chose as preferred is paused or revoked, and
    // WhooshBang never picks another for them. Nothing was sent.
    break;
}

WhooshBang resolves the subscriber’s active binding on the notifier’s current default connection and snapshots that exact connection for delivery. When the message reaches provider_accepted, Telegram has created it through the bot the subscriber authorized. A later default-connection change never reroutes this accepted message.

Omitting delivery_target selects connection, which is what an ordinary send wants. Naming it explicitly costs nothing and makes the intent obvious in a code review.

​
5. Read the message state

curl --silent --show-error \
  --header "Authorization: Bearer $WHOOSHBANG_CREDENTIAL" \
  "https://api.whooshbang.com/v1/messages/$MESSAGE_ID"
const current = await whooshBang.getMessage(message.id);
console.log(current.state, current.delivery.highest_proven_provider_state);

Within a moment the message reaches provider_accepted:

{
  "id": "msg_synthetic_001",
  "project_id": "project_synthetic",
  "environment": "test",
  "subscriber_id": "customer_123",
  "notifier_id": "default",
  "binding": {
    "id": "binding_synthetic_001",
    "project_id": "project_synthetic",
    "environment": "test",
    "subscriber_id": "customer_123",
    "notifier_id": "default",
    "channel": "telegram",
    "status": "active"
  },
  "delivery_target": "simulator",
  "state": "provider_accepted",
  "accepted_at": "2026-07-25T17:45:00Z",
  "updated_at": "2026-07-25T17:45:02Z",
  "expires_at": "2026-07-26T17:45:00Z",
  "delivery": {
    "attempt_count": 1,
    "highest_proven_provider_state": "provider_accepted",
    "retry_scheduled": false
  },
  "diagnostic_id": "diag_message_provider_001",
  "correlation_id": "export_987",
  "metadata": {
    "job": "export_987"
  }
}

provider_accepted means Telegram accepted the message and created it. It does not mean a device displayed it, or that a person read it. WhooshBang never reports delivered for Telegram — see what the states mean.

Retries, terminal failures and ambiguous outcomes are all states this field can reach. What the states promise says what each one commits to before you write code that branches on them.