Receives App Store Server Notifications v2, verifies JWS signatures using Apple's official library, and forwards events to Telegram or ntfy.
- Apple sends a signed POST request to your webhook endpoint after each subscription event
- The server verifies the JWS signature using Apple's root certificates
- A formatted message is sent to Telegram and/or ntfy
- Open Telegram and search for @BotFather
- Send
/newbotand follow the prompts (choose a name and username) - BotFather will reply with your Bot Token — looks like
123456789:ABCDefghIJKlmnopQRStuvWXyz - Copy it — this is your
TELEGRAM_BOT_TOKEN
For a personal chat:
- Search for @userinfobot on Telegram
- Send it any message
- Open this URL in your browser (replace
<TOKEN>with your bot token):https://api.telegram.org/bot<TOKEN>/getUpdates - Find
"chat":{"id":...}in the response — the negative number (e.g.-1001234567890) is yourTELEGRAM_CHAT_ID
For a group or channel:
- Add your bot to the group/channel and grant it permission to post messages
- Send a message in the group, then open this URL in your browser (replace
<TOKEN>with your bot token):https://api.telegram.org/bot<TOKEN>/getUpdates - Find
"chat":{"id":...}in the response — the negative number (e.g.-1001234567890) is yourTELEGRAM_CHAT_ID
cp .env.example .env.localEdit .env.local:
# Notification provider: "telegram" (default) | "ntfy" | "both"
NOTIFICATION_PROVIDER=telegram
# Telegram (required if provider is "telegram" or "both")
TELEGRAM_BOT_TOKEN=123456789:ABCDefghIJKlmnopQRStuvWXyz
TELEGRAM_CHAT_ID=-1001234567890
APPLE_ENABLE_ONLINE_CHECKS=
# ntfy (required if provider is "ntfy" or "both")
NTFY_URL=https://ntfy.sh/your-random-topic
NTFY_TOKEN= # optional — only for protected topics
# One line per app: APP_<SLUG>=<bundleId>:<appAppleId>
APP_IMAGE2WEBP=com.yourcompany.image2webp:1234567890
APP_MYAPP=com.yourcompany.myapp:9876543210| Variable | Description |
|---|---|
NOTIFICATION_PROVIDER |
telegram (default), ntfy, or both |
TELEGRAM_BOT_TOKEN |
Token from @BotFather |
TELEGRAM_CHAT_ID |
Your personal ID (positive number) or group/channel (negative) |
APPLE_ENABLE_ONLINE_CHECKS |
true enables Apple's online OCSP revocation checks; empty/false skips them |
NTFY_URL |
Full ntfy topic URL, e.g. https://ntfy.sh/my-secret-topic |
NTFY_TOKEN |
Bearer token — only needed for access-controlled topics |
APP_<SLUG> |
<bundleId>:<appAppleId> — slug becomes the URL path |
ntfy tip: The topic name acts as a shared secret — anyone who knows it can subscribe. Use a long random string, e.g.
https://ntfy.sh/appstore-k8f2mxq79p. No account required for public topics.
Go to App Store Connect → Your App → App Information → App Store Server Notifications:
- Sandbox URL — your ngrok/tunnel URL (for testing)
- Production URL — your deployed Vercel URL
Both should point to POST /api/apple/<app-slug>, e.g. /api/apple/image2webp.
Install dependencies:
pnpm installStart the local server:
pnpm local
# Listening on http://localhost:3003Expose it to the internet with ngrok:
ngrok http 3003Paste the ngrok HTTPS URL into App Store Connect → Sandbox Server URL.
pnpm run deployThen go to Vercel Dashboard → Your Project → Settings → Environment Variables and add NOTIFICATION_PROVIDER, the relevant provider vars (TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID / NTFY_URL / NTFY_TOKEN), and all your APP_* variables.
Paste the production Vercel URL into App Store Connect → Production Server URL.
- Go to App Store Connect → Users and Access → Sandbox Testers
- Click + to create a new tester
- Fill in the details — use a real email address you control (it will receive a verification email)
- Choose a country/region that matches the currency you want to test
- Click Invite and verify the email address
Each sandbox account can only be used on one device at a time. Create multiple accounts if you need to test on multiple devices.
- On your iPhone, go to Settings → Developer → Sandbox Apple Account
- Sign in with your sandbox tester account
No need to sign out of your real Apple ID. The sandbox account is managed separately under Developer settings.
Make sure your webhook Sandbox URL is set in App Store Connect:
App Store Connect → Your App → App Information → App Store Server Notifications → Sandbox Server URL
Set it to your ngrok URL (local dev) or your Vercel URL.
- Open your app on the iPhone
- Tap any in-app purchase or subscription button
- A sign-in prompt will appear — sign in with your sandbox tester account (not your real Apple ID)
- Confirm the purchase (no real money is charged)
- Watch your Telegram chat for the notification
Sandbox subscriptions renew much faster than production:
| Production duration | Sandbox renewal interval |
|---|---|
| 1 week | 3 minutes |
| 1 month | 5 minutes |
| 2 months | 10 minutes |
| 3 months | 15 minutes |
| 6 months | 30 minutes |
| 1 year | 1 hour |
Each subscription auto-renews up to 12 times in sandbox, then expires — you'll receive DID_RENEW and eventually EXPIRED notifications.
To cancel, refund, or change a sandbox subscription:
iPhone → Settings → Developer → Sandbox Apple Account
From here you can sign in with your sandbox tester and manage active subscriptions directly.
Add a line to your .env.local (no code changes needed):
APP_MYAPP=com.yourcompany.myapp:1234567890The variable suffix (MYAPP → myapp) becomes the URL slug: POST /api/apple/myapp.
Then add the Vercel environment variable in the dashboard and redeploy.
POST /api/apple/:app
Always returns 200 OK to Apple (even on verification failure) to prevent retries.
This project is built and maintained to support Image2WebP — convert images to WebP/AVIF format quickly and securely, reducing file size by up to 80%.
MIT © 2026 givebest
