Local setup
May 18, 2026 · View on GitHub
This walks through running DevPinger on your laptop with a real Telegram bot and real GitHub / Jira OAuth. The whole thing fits in ~30 minutes the first time.
Prerequisites
- Node 22 (
brew install node@22ornvm install 22) - pnpm 10 (
npm i -g pnpm) - Docker Desktop, OrbStack, or Colima — to run Postgres + Redis
cloudflared(brew install cloudflared) — for a stable public URL- A Telegram account with the ability to talk to
@BotFather
1. Create a Telegram bot
In Telegram, open @BotFather:
/newbot
<name your bot, e.g. DevPinger Dev>
<username, must end in `bot`, e.g. devpinger_dev_bot>
Save the token BotFather hands you — it goes into TELEGRAM_BOT_TOKEN.
The username (without @) goes into TELEGRAM_BOT_USERNAME.
2. Get a stable public URL via Cloudflare Tunnel
Telegram webhook + GitHub OAuth callback both need a stable HTTPS URL that points at your laptop. A named Cloudflare Tunnel gives you exactly that without rotating the URL on every reboot.
cloudflared tunnel login # auth in browser
cloudflared tunnel create devpinger-dev # creates tunnel + credentials
If you have a domain in Cloudflare (e.g. devpinger.com):
cloudflared tunnel route dns devpinger-dev dev.devpinger.com
Then create ~/.cloudflared/devpinger-dev.yml:
tunnel: <tunnel-id>
credentials-file: /Users/<you>/.cloudflared/<tunnel-id>.json
ingress:
- hostname: dev.devpinger.com
service: http://localhost:3001
- service: http_status:404
Start the tunnel (keep it running in a separate terminal):
cloudflared tunnel run devpinger-dev
Your stable URL is now https://dev.devpinger.com → localhost:3001.
If you don't have a domain in Cloudflare, you can use the free quick tunnel:
cloudflared tunnel --url http://localhost:3001
…but the URL rotates on every restart, so you'll be updating GitHub /
Jira OAuth callbacks each time. A .app / .dev domain through
Cloudflare Registrar runs ~$10/year and saves the hassle.
3. Create the GitHub OAuth App
GitHub Settings → Developer settings → OAuth Apps → New OAuth App:
| Field | Value |
|---|---|
| Application name | DevPinger Dev (or anything) |
| Homepage URL | https://dev.devpinger.com |
| Authorization callback URL | https://dev.devpinger.com/oauth/github/callback |
Save Client ID → GITHUB_OAUTH_CLIENT_ID. Generate a client secret →
GITHUB_OAUTH_CLIENT_SECRET.
4. Create the Atlassian (Jira) OAuth App
developer.atlassian.com → My Apps → Create → OAuth 2.0 (3LO).
Scopes:
read:jira-userread:jira-workwrite:jira-workoffline_access
Callback URL: https://dev.devpinger.com/oauth/jira/callback
Save Client ID and Secret into JIRA_OAUTH_CLIENT_ID /
JIRA_OAUTH_CLIENT_SECRET.
5. Generate ENCRYPTION_KEY
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Copy the 64-char hex string into ENCRYPTION_KEY.
Generate TELEGRAM_WEBHOOK_SECRET the same way (any 16+ random
characters work — openssl rand -hex 32 is fine too).
6. Fill out .env
cp .env.example .env
$EDITOR .env
At minimum, set:
PUBLIC_BASE_URL=https://dev.devpinger.comTELEGRAM_BOT_TOKEN,TELEGRAM_BOT_USERNAME,TELEGRAM_WEBHOOK_SECRETENCRYPTION_KEYGITHUB_OAUTH_CLIENT_ID,GITHUB_OAUTH_CLIENT_SECRET,GITHUB_OAUTH_REDIRECT_URI=https://dev.devpinger.com/oauth/github/callbackJIRA_OAUTH_CLIENT_ID,JIRA_OAUTH_CLIENT_SECRET,JIRA_OAUTH_REDIRECT_URI=https://dev.devpinger.com/oauth/jira/callback
The rest can stay at defaults.
7. Run it
pnpm install
docker compose up postgres redis -d
pnpm db:migrate
pnpm dev # turbo runs server + worker in watch mode
Open Telegram, find your bot, send /start. You should get the welcome
screen with "Connect GitHub" and "Connect Jira" buttons.
8. Wire a repo
In the bot:
- Tap Connect GitHub, complete the OAuth flow in the browser. You're redirected back to Telegram.
- Send
/repos— the bot lists repositories your token can access and gives each row a ➕ button. Tap to subscribe; webhook registration on the GitHub side happens automatically. Same flow for/projectswith Jira. - Push a PR in that repo and watch it land in the bot.
Troubleshooting
/startsays nothing — Telegram long-polling is still finishing itsdeleteWebhookclear. Wait 5s and resend.- OAuth callback 404s — your tunnel isn't routing or the callback
URL in the OAuth app doesn't match
PUBLIC_BASE_URL. Check both. ENCRYPTION_KEY must be 64 hex characters— the env validator refuses anything else; regenerate with thenode -ecommand above.- Webhook deliveries fail with 401 — the secret stored on the
GitHub/Jira side doesn't match what V1 minted at subscription
time. V1 generates a fresh per-subscription secret on the GitHub
side automatically; if you're poking at the SQL directly, re-run
the
/reposadd flow instead. The HMAC matching logic lives in apps/server/src/services/ingest.ts and services/github-signature.ts.