Skip to main content
Tappify never runs your code on its servers. An extension that answers procedures, syncs metrics or performs actions runs its own server, and the starter ships one handler behind two entries.

One handler, two entries

server/handler.ts exports the options and the handler:
It is a (Request) => Promise<Response>, so it runs anywhere the Fetch API does. The options are exported separately because a platform that mounts you under a prefix needs basePath.
server/worker.ts:
wrangler.toml:

Point the manifest at it

sandboxBaseUrl is used for dev installs and sandbox projects. It defaults to baseUrl. Both must be https.

The routes Tappify calls

createTappifyHandler routes all of them; you fill in the ones you declared. A path it routes with no handler registered comes back as a 404 naming the missing one, so a contribution you declared and never wired is loud rather than silent.

Verifying the caller

Every route except health carries Authorization: Bearer <install token> — RS256, five minutes, audience ext:<your extension id> — and X-Tappify-Event-Id, the idempotency key. The handler verifies the token for you against Tappify’s JWKS. Verify it yourself only when you are not using the handler:
The keys are at https://api.tappify.ai/api/v1/extensions/.well-known/jwks.json and rotate with a 24-hour overlap, so both keys are served through the change.

Before you publish

Health has to answer 200 within three seconds, on baseUrl and on sandboxBaseUrl if you declared one, or the publish stops. A redirect counts as a failure: serve the route from the url you declared. tappify extension dev --live serves your bundle from your machine but still relays procedures to the url in your manifest. Run the handler locally with pnpm run dev:worker, give it a public address, and pass that to --server <url> for the length of the session — Tappify calls you from its own servers, so a localhost url is not one it can reach. See Local server during live preview.