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
  }
}
  1. Show pairingUrl as a QR code, or show userCode so the person can type it in the app (Scan → Enter code instead).
  2. Poll every pollInterval seconds 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