QR Connect
QR Connect is the easiest way for something to start sending you notifications. It asks, you scan a QR code (or type a short code) in the PushTower app, you approve, and it receives a channel link that can send to one channel and nothing else.
It needs no API key and no account. The whole setup is one scan.
Building a website where each of your users should get notifications? See Add PushTower to your website for the embeddable widget and callback flow.
$ pushtower connect --name "Claude Code" --env-file .env
Scan with your phone to let "Claude Code" send you notifications:
█▀▀▀▀▀█ ▄▀▄ ▀▄ █▀▀▀▀▀█
█ ███ █ ▀█▄▀██ █ ███ █
...
or in the PushTower app tap Scan and enter the code KXPD-4821
Waiting for approval (expires in 10 minutes)…
Connected.
In the app
Tap the QR code button on the Channels screen (or Settings → Connected services → Scan QR code). You can also scan the code with the iPhone camera, which opens the app directly. The app shows who is asking and lets you pick where it may send: a new channel or one you already have. Allow creates the link; Deny tells the requester no.
Every connection appears under Settings → Connected services, where you can disconnect it at any time.
Anyone can type any name, so the name shown is not verified. Only approve requests you just started yourself.
CLI
# Interactive: shows the QR code and waits
pushtower connect --name "Backup script" --channel-name "Backups" --env-file .env
With --env-file, the link is written to that file as PUSHTOWER_LINK (or
--env-var NAME) and never printed. Without it, the link is printed as JSON.
For agents, which usually only see a command’s output after it exits, split it in two:
pushtower connect start --name "Claude Code" --channel-name "my-project"
# → prints pairingUrl, userCode, qr, sessionId and pollToken as JSON.
# Show the person the pairingUrl and userCode (and the qr text), then:
pushtower connect wait --session-id <sessionId> --poll-token <pollToken> --env-file .env
HTTP
The same flow in plain HTTP, with no SDK or key:
# 1. Start a request
curl -X POST https://api.pushtower.com/api/connect/sessions \
-H "Content-Type: application/json" \
-d '{"clientName": "Claude Code", "channelName": "my-project"}'
{
"success": true,
"data": {
"sessionId": "4f0c…",
"pollToken": "t8Qm…",
"pairingUrl": "https://pushtower.com/connect?code=AbC…",
"userCode": "KXPD-4821",
"expiresAt": "2026-09-29T09:10:00.000Z",
"pollInterval": 2
}
}
- Show
pairingUrlas a QR code, or showuserCodeso the person can type it in the app (Scan → Enter code instead). - Poll every
pollIntervalseconds until the status changes:
curl -X POST https://api.pushtower.com/api/connect/sessions/$SESSION_ID/poll \
-H "Content-Type: application/json" \
-d "{\"pollToken\": \"$POLL_TOKEN\"}"
status |
Meaning |
|---|---|
pending |
Not answered yet; keep polling |
approved |
Includes link (url, key, channelId, channelName); store it |
denied |
The person said no |
expired |
Not answered within 10 minutes; start a new request |
claimed |
The link was already collected by an earlier poll |
The link is returned exactly once, on the first poll after approval.
SDK
import { PushTowerConnect } from '@pushtower/client';
const connect = new PushTowerConnect();
const session = await connect.start({ clientName: 'Backup script' });
console.log(`Scan ${session.pairingUrl} or enter ${session.userCode}`);
const link = await connect.waitForLink(session); // { url, key, channelId, … }
Security
- The QR code carries a single-use pairing code, not a credential. It expires after 10 minutes, and approving it requires a signed-in PushTower app.
- Only the requester holds the poll token, so only it can collect the link. Everything is stored hashed, and the link is created at the moment it is collected.
- The result is a channel link: it can send to one channel, cannot read anything, and can be revoked from Connected services.
- Rate limits: 10 new requests per minute per IP; polls up to 2 per second.