Run the staff channel on live Telegram
Take the integration off the mock: a real bot posts the application card, a real button press flips the application, and the card edits itself.
Owned by Venue Manager · 16 steps · about 18 minutes
Why this exists
Telegram is the venue manager's primary channel, so intake's messages were built to be acted on from the chat rather than merely read there. A new application posts a card carrying the headline facts and three buttons: a Review deep link, Fast-Track Accept and Decline. Approving from the chat flips the application for real — same transaction, same contract draft, same emails — and then edits the card in place so the chat stops offering a decision that has already been made.
All of that already worked against a mock. What is new is that there is now a real bot at the other end, and a real button press has somewhere to arrive.
One module carries bytes; nothing about the buttons changed
The telegram module owns no business logic. Every button's meaning still lives where it always did — intake's shared callback dispatcher and the prefixes registered on it. A real press is normalised into exactly the payload the existing handlers already read and dispatched into the same registry the test simulator uses, which is why not one handler needed changing and why a handler cannot tell a real press from a simulated one.
The mock wins every tie
Mock is the default and it never raises. Live requires the mode to be set to live and a bot token to be present. Missing token, test environment, running under the test suite, an unrecognised mode, or a live adapter that fails to construct — every one of those falls back to the mock with a warning rather than breaking the app. The test suite is offline by construction, three separate ways.
Consequence worth knowing before you debug anything: a mode change needs a process restart, because intake caches its own adapter registry when the module is imported.
What the live adapter fixes before it sends
Four rewrites, each because the API would otherwise reject the whole message. Placeholder chat ids from the seed are not chat references and are replaced by the configured default. Button URLs pointing at localhost are rewritten onto the public base URL — Telegram validates button URLs and rejects the entire message on a bad one, so a single stale base URL setting would silence every card the platform ever sends. Text is clamped. The message id is coerced to the type the API wants. What gets recorded is what actually went on the wire, so the console never lies about what was sent.
The inbound door fails closed
There is exactly one new public endpoint, and it is sessionless. It authenticates on a secret header compared in constant time. A wrong secret, a missing secret, or a secret that was never configured all produce a refusal with no side effects at all — no stored update, no dispatch, no callback answered. Everything that gets past that is idempotent on the update id, claimed in its own committed transaction before anything runs, because Telegram retries until it gets a success and a retried button must not act twice.
Honest status
The channel envelope is implemented to the documented Bot API semantics and unit tested for channels, supergroups and inaccessible messages, but no live press has been confirmed against the real bot yet. That is why the first one is built to be fully diagnosable: the raw callback is stored verbatim and the delivery is listed in the admin console, so a surprise degrades the recorded context rather than the action — the only load-bearing field is the button's own data, which is identical in every chat type.
Before you start
- An admin session for the console, the test send and the webhook registration.
- A venue_manager or admin session to act on an application.
- For a genuinely live run: a bot token, a default chat id, a webhook secret and a public HTTPS base URL, all in the server environment.
- On a default install everything below still works — against the mock, with every call recorded instead of sent.
Practise with
| Persona | Password | Note | |
|---|---|---|---|
| admin | admin@club.test | admin123 | the only role that can reach the Telegram console or register the webhook |
| venue_manager | manager@club.test | manager123 | acts on cards in the channel; cannot open the Telegram console |
| host_promoter | host@club.test | host123 | the applicant whose submission produces the card |
Steps 1–4 — Admin
their manual →Owns the Telegram console, the webhook registration and every credential the integration needs.
-
1Open the Telegram console. It shows the mode, whether a bot token and a webhook secret are configured, the destination chat, the deep-link base URL, any warnings, and the last fifteen sends and inbound updates.Expected result Mode reading mock on a default install, live once the environment says so.Watch out for The token and the secret are reported as booleans. No endpoint on this platform ever returns either value — the console is admin-viewable and on a demo instance the admin login is published in the banner.
-
2Send a test message before you trust the channel with real cards.Expected result A message in the chat, and a new row in the sends list.Watch out for If this succeeds against the mock and fails live, read the recorded request rather than guessing: the resolved chat id and any rewritten button URLs are in it, which is usually where the answer is.
-
3Register the inbound webhook so button presses have somewhere to arrive. This is a deploy step and it does not happen automatically.Expected result Telegram accepts the URL and starts delivering updates.Watch out for HTTPS only, and it refuses outright without a configured secret. That refusal is the feature: an endpoint with no secret rejects every delivery anyway, so registering one would just create a channel that silently never works.
-
4Ask Telegram what it thinks the current webhook is, including its pending update count and last error.Expected result The registration as the API sees it, not as we hope it is.Watch out for A climbing pending count with a repeated last error is the signature of a secret mismatch: we are refusing every delivery and Telegram is retrying every one of them.
Steps 5 — Host / Promoter
their manual →Is the applicant on the other end of whichever button gets pressed.
-
5Submit an application. That is the only thing the applicant does in this workflow — everything after it happens in the chat.Expected result A 201, and a card in the venue's staff channel within the same request.Watch out for If the send fails, the application is still created. A telegram failure is recorded as a note on the application and never rolls the submission back — losing a booking because a chat was down would be absurd.
Steps 6–11 — Venue Manager
their manual →-
6Read the card in the channel before touching a button: event name, date, headcount, the viability score and band, whether artwork was attached, and three buttons — Review Application, ✅ Fast-Track Accept, ❌ Decline.Expected result One card per application, edited in place as the application moves.Watch out for The presser is read from who tapped, not from who posted. In a channel the post is authored by the channel itself, so 'who approved this' comes from the press — which is why the audit still names a person.
-
7Press ✅ Fast-Track Accept when the answer is obviously yes. The press travels to the inbound endpoint, is dispatched into intake's callback handler, and approves the application.Expected result A toast in the chat confirming the outcome, and the card edited to show it.Watch out for The accept and decline buttons carry single-use tokens with a seventy-two hour life, consumed with a guarded update — a double tap cannot double-act, and the second one is told the token was already used.
-
8Open the application and confirm what the press actually did: status approved, the transition recorded with telegram as its source, a contract drafted, the applicant emailed.Expected result The same outcome as approving from the dashboard, because it is the same code path.Watch out for Fast-tracking from a chat is exactly as powerful as approving from a desk. Pressing accept is signing the venue up to the numbers on that application.
-
9For anything less obvious, press Review Application instead. It opens the application in the browser and, if it was still a new submission, claims it into under review with the source recorded as telegram.Expected result The full application, already marked as picked up.Watch out for That claim is real work recorded against you. Opening the link out of curiosity marks the application as being reviewed — do not tap it unless you are going to.
-
10Check the board afterwards. Every card in the chat deep-links to whatever is actionable next for that application, not to this board — lock the contract, chase the signature, counter-sign, convert, or just the audit trail.Expected result The pipeline, with your application in its new column.Watch out for Those deep links carry anchors into specific panels. A card sitting in somebody's chat history is a live link to a control, which is exactly why those anchors are never renamed.
-
11Know what a stale card can and cannot do. Any transition to approved or rejected invalidates every unused token on that application, including the host's edit link.Expected result Yesterday's card cannot re-decide today's decision.Watch out for The buttons go dead but the card keeps its next-step link on purpose — the message stays useful after the decision instead of becoming a dead end in the chat.
Steps 12–16 — Admin
their manual →Owns the Telegram console, the webhook registration and every credential the integration needs.
-
12Understand the contract of the one public inbound endpoint, because every live failure is a failure of one of its five stages: secret, envelope, idempotency, dispatch, answer.Expected result A refusal with no side effects for a bad secret; a bad envelope rejected; everything else answered with success.Watch out for It returns success for an unknown button prefix and even for a handler that raised. That is deliberate: the state change is already durable, and a failure response would make Telegram retry a button that already ran.
-
13Read the inbound list on the console to see the deliveries that actually arrived, with their update ids, chat types and button data.Expected result One row per update, including duplicates marked as such.Watch out for A delivery recorded here with a duplicate flag is Telegram retrying, not somebody pressing twice. Both are normal; only the first one ran.
-
14When a button lands somewhere unhelpful, print the computed deep link for every application state in one table.Expected result The exact link each state produces, and why.Watch out for This view never mints a token, so reading it cannot accidentally claim an application. That restraint is why it is safe to open on a live system.
-
15When somebody swears no card arrived, read the recorded outbound traffic. Live calls are recorded identically to mock ones — including failures, with their status and the API's own description.Expected result The calls correlated to that application id, successful or not.Watch out for The bot token appears in none of it. It exists in the process environment only, is used solely to build the request URL, and is scrubbed from any error message before it is re-raised.
-
16Remove the webhook to take the integration back off live — for maintenance, or to hand the bot to another environment.Expected result Telegram stops delivering; the console reflects it.Watch out for One caller does not swallow adapter errors: the tax module's approval message deliberately lets a failure surface, so a live outage becomes a server error on that one admin action and rolls its transaction back. That is unchanged behaviour, but on live it can now be triggered by the network rather than only by a test.