One-click deploy provisions the Worker and D1. Afterwards, apply migrations and set the secrets (
ADMIN_PASSWORD,VAPID_PRIVATE_KEY) — see Setup.
Yield is a self-hosted App Store Connect (ASC) order-management console, fed entirely by App Store Server Notifications V2 — no API polling. It verifies, stores, and visualizes your in-app purchase and subscription events, then fans them out as push notifications.
Built on Next.js 16 + Cloudflare Workers (via OpenNext) + D1.
- Overview — KPI cards + charts (orders, revenue via FX, active subscriptions, refund rate), with environment (All / Production / Sandbox) and 7-day / 30-day range switchers.
- Orders — filter by type, storefront, transaction status, subscription status, and environment; sort by amount and date; pagination with page-size control; detail drawer.
- Notifications — filter by event type, subtype, and environment; sortable; detail drawer.
- Bidirectional drill-down — jump between an order and its related notifications.
- Settings — display currency, product-ID → label mapping, push device IDs (Bark / Orange Cloud), push language, and a browser-notification toggle.
- Webhook ingestion — ASSN V2 with full JWS signature verification (x5c chain pinned to Apple Root CA G3) and idempotent storage.
- Push — Bark + Orange Cloud (Bark V2 protocol) + Web Push (VAPID, pure WebCrypto). Content is localized; the Bark group is the app's App Store display name (resolved from
appAppleIdvia the iTunes Lookup API). - PWA — installable, with a service worker and app icons.
- i18n & theming — English + 简体中文 (next-intl); light / dark / system themes.
Next.js 16 (App Router, Turbopack) · React 19 · @opennextjs/cloudflare → Cloudflare Workers · Cloudflare D1 · next-intl · Tailwind CSS v4 · shadcn/ui · next-themes · Recharts · jose + @peculiar/x509 (JWS verification).
App Store Connect ──(ASSN V2, signed JWS)──▶ POST /api/asc/webhook
│ verify (jose + x5c chain → Apple Root CA G3)
▼
Cloudflare D1
(notifications · transactions · subscriptions)
│
▼
Bark · Orange Cloud · Web Push (localized, new events only)
The webhook endpoint is intentionally public — Apple must reach it. Its only security boundary is JWS signature verification; there is no shared secret. The admin console is gated by a single secret (ADMIN_PASSWORD, an HMAC-signed session cookie).
- Node.js 20+ and pnpm
- A Cloudflare account and Wrangler (
npx wrangler …)
-
Install
pnpm install
-
Create a D1 database and wire it into the
DBbinding inwrangler.jsonc(setdatabase_name/database_id).npx wrangler d1 create yield
Forking?
wrangler.jsoncalso holds instance-specific values to change: the workername(and the matchingWORKER_SELF_REFERENCEservice), and theroutescustom domain (set to the author'syield.o-c.do— remove it or point it at your own). -
Apply migrations (local and remote):
npx wrangler d1 migrations apply <database_name> --local npx wrangler d1 migrations apply <database_name> --remote
-
Generate a VAPID key pair (for Web Push):
node -e 'const{webcrypto:w}=require("crypto");w.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},true,["sign","verify"]).then(async k=>{const p=Buffer.from(await w.subtle.exportKey("raw",k.publicKey)).toString("base64url");const j=await w.subtle.exportKey("jwk",k.privateKey);console.log("VAPID_PUBLIC_KEY="+p);console.log("VAPID_PRIVATE_KEY="+j.d)})' -
Configure environment
- Local:
cp .dev.vars.example .dev.vars, then fill inADMIN_PASSWORDand the VAPID values. - Production:
npx wrangler secret put ADMIN_PASSWORDnpx wrangler secret put VAPID_PRIVATE_KEY- put
VAPID_PUBLIC_KEYandVAPID_SUBJECTinwrangler.jsonc→vars.
- Local:
-
Run locally
pnpm dev
-
Deploy
pnpm run deploy
-
Point App Store Connect — set the App Store Server Notifications V2 URL (Production and/or Sandbox) to:
https://<your-worker-domain>/api/asc/webhook
| Name | Kind | Description |
|---|---|---|
ADMIN_PASSWORD |
secret | Admin login secret / session signing key |
VAPID_PUBLIC_KEY |
var | Web Push public key (base64url) |
VAPID_PRIVATE_KEY |
secret | Web Push private key |
VAPID_SUBJECT |
var | VAPID contact (mailto: or https:) |
NEXTJS_ENV |
var | development for local dev |
The D1 binding DB is declared in wrangler.jsonc.
- Bark / Orange Cloud — set the device IDs in Settings; both speak the Bark V2 protocol. Revenue events get a time-sensitive alert + sound; sandbox events are delivered silently. The notification group is the app's App Store display name.
- Web Push — open Settings → Browser notifications → Enable (requires HTTPS, or
localhost). Subscriptions live in D1; payloads are encrypted with WebCrypto (RFC 8291 / RFC 8292). - Push content language is chosen separately in Settings, since the webhook has no UI locale.
migrations/ D1 schema (notifications, transactions, subscriptions, settings, products, push_subscriptions)
public/ manifest, service worker, app icons
src/app/[locale]/ localized routes; the /admin console lives under (panel)
src/app/api/ asc/webhook, push/subscribe, push/unsubscribe
src/lib/asc/ verify · store · queries · notify · web-push · fx · labels · storefronts · settings
src/components/ UI primitives + admin components
src/i18n/ · src/messages/ next-intl config + en / zh-Hans translations
MIT © chen2he
