Files
readest/apps/readest-app/wrangler.toml
T
Huang Xin 4fa7f76bc1 feat(payment): observability for store subscription webhooks (#4704)
* feat(payment): observability for store subscription webhooks

Add monitoring for the App Store / Google Play webhooks so store-side
subscription changes are observable on Cloudflare, where stored Workers
Logs are head-sampled at 1% and would miss almost all low-volume webhook
events.

- Add iap/telemetry.ts: every webhook invocation emits a structured log
  line (streamed in full by `wrangler tail`) and a Cloudflare Analytics
  Engine data point (100% capture, independent of log sampling). Writes
  no-op off the Worker runtime, mirroring the getCloudflareContext guard
  in deepl/translate.ts.
- Instrument both webhook routes to record outcome (handled, skipped,
  rejected, error), notification type, status, reason, and latency on
  every return path.
- Add GET /api/cron/iap-reconcile: a CRON_SECRET-protected sweep that
  counts drift (rows still active while their store expiry has passed = a
  missed webhook) in both IAP tables and records a reconcile metric.
  Detection-only; never mutates state.
- Add the IAP_WEBHOOK_AE Analytics Engine binding to wrangler.toml.

New configuration: CRON_SECRET (reconcile auth) and an iap_webhooks
Analytics Engine dataset bound as IAP_WEBHOOK_AE. The reconcile route is
triggered on a schedule (a Cloudflare Cron Trigger worker that fetches
the URL, or any external scheduler) with an Authorization bearer header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(payment): move reconciliation to a dedicated cron Worker

Replace the public CRON_SECRET-protected /api/cron/iap-reconcile route
with a dedicated Cloudflare Cron Worker. A Cron Trigger invokes the
worker's scheduled() handler directly, so there is no public HTTP surface
and no shared request secret to manage - the strongest option on
Cloudflare (OpenNext's generated worker only exports `fetch`, so the main
worker cannot host a scheduled() handler).

- Add workers/iap-reconcile: a self-contained worker (own package.json,
  tsconfig, wrangler.toml) matching the existing workers/send-email
  convention, registered in pnpm-workspace.yaml. Hourly Cron Trigger;
  reads the IAP tables via the Supabase service role and records a drift
  metric to the shared iap_webhooks Analytics Engine dataset.
- Reconcile logic lives in workers/iap-reconcile/src/reconcile.ts and is
  unit-tested from the app suite.
- Remove the public route and its test; drop the now-unused
  recordIapReconcile from iap/telemetry.ts (webhook telemetry unchanged).

Configuration: set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY as secrets
on the worker and deploy it with `wrangler deploy` from its directory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:02:02 +02:00

50 lines
1.1 KiB
TOML

name = "readest-web"
main = ".open-next/worker.js"
compatibility_date = "2026-04-10"
compatibility_flags = ["nodejs_compat"]
workers_dev = true
preview_urls = true
[[routes]]
pattern = "web-cf.readest.com"
zone_name = "readest.com"
custom_domain = true
[[routes]]
pattern = "api-cf.readest.com"
zone_name = "readest.com"
custom_domain = true
[[routes]]
pattern = "web.readest.com"
zone_name = "readest.com"
custom_domain = true
[vars]
PNPM_VERSION = "10.28.1"
[observability]
enabled = true
head_sampling_rate = 0.01
logs = { head_sampling_rate = 0.01 }
traces = { head_sampling_rate = 0.01 }
[assets]
directory = ".open-next/assets"
binding = "ASSETS"
[[kv_namespaces]]
binding = "TRANSLATIONS_KV"
id = "fa6e3eb1988a424bbf3c952e01650ced"
[[r2_buckets]]
binding = "NEXT_INC_CACHE_R2_BUCKET"
bucket_name = "readest-next-inc-cache"
# Per-event metrics for the App Store / Google Play subscription webhooks and the
# reconciliation sweep. 100% capture, independent of the head-sampled Workers
# Logs above. Queryable via the Analytics Engine SQL API.
[[analytics_engine_datasets]]
binding = "IAP_WEBHOOK_AE"
dataset = "iap_webhooks"