- Start here
- Send your first notification
Start here
Send your first notification
From an empty organization to a message on a real phone, in three commands, then the same journey through the API.
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 ownfetch) - 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.
Before your first link
Check these in order; every resource must belong to the same project and environment fixed by the credential:
- Create the project and choose its test or live environment.
- Activate and route a channel connection for the requested channel. The CLI’s
default
project createdoes this for test; API-created projects may need the explicit channel setup. - Use an active notifier. Every new project has an active
Defaultnotifier, so omitnotifier_idunless you deliberately created another. - 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.
- 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.
What to read next
- The command line reference — every command and every argument, when you want the terminal rather than the API.
- Idempotency — before you put a send behind a retrying job queue.
- What “sent” means — before you show a status to your own users.
- Diagnosing a failure — before you need it at 3am.