Skip to main content
Your server sends one signed request; Tappify decides where it lands. A webhook declared as an alert reaches the owner’s inbox, one declared as a banner sits at the top of a page until dismissed, and one declared as an event reaches your own widgets and, when a marker names it, the time axis of a chart.
A command that declares something your server answers also writes a server.baseUrl when the manifest has none, using the placeholder https://<your-id>.example.com. That address answers nothing, so tappify extension publish stops on its health check until you point baseUrl at a server you have deployed.

The manifest entry

The command writes this entry. You can hand-edit tappify.extension.json instead — the $schema line gives your editor completion and validation, and tappify extension doctor checks the result. An event name is domain.verb, both parts snake_case, and one webhook carries one as. A banner contribution names a webhook you declared with as: "banner", and a marker names one you declared with as: "event"; naming an event nothing declares under that as fails the manifest schema. An event you want in the inbox and at the top of a page is two webhooks under two names, as anomaly.detected and anomaly.spiked are above — Tappify resolves a delivery by its name alone, so one name never carries two outcomes. An alert or a banner payload must declare a required why string. That sentence is the whole message the owner reads, so write it as one: Installs fell 40% in an hour, not ANOMALY_DETECTED. A marker’s chart is one host chart or "*" for every one. Its glyph is at most two characters and Tappify draws it, inside its own axis, with your tile on hover. Declaring an alert or a banner needs alerts:write; a marker needs ui:render as well, because it draws in the host.

Sending one

sendEvent builds the envelope, signs it, and posts it.
The event name and the payload type both come from your generated types, so a name you never declared or a missing why is a compile error.

The envelope, if you send it yourself

The signature is the lowercase hex HMAC-SHA256 of the exact bytes you send, keyed on the inbound secret from your extension’s Server page. source is your extension id, and a source that is not yours is refused. build is the owner’s app version or build number when you know it, which is what lets an alert land against a release. Tappify answers 202 to every delivery it stores, whatever the delivery became.

Resending

dedupeKey is yours to choose and Tappify stores it per install; leave it out and sendEvent sends a fresh UUID, which makes every retry a new delivery. Send the same key twice and the second delivery is recorded as a duplicate: no second alert, no second banner, no error. That makes a retry loop on your side safe.

Limits

Sixty deliveries a minute per install. Three alerts a day per project — a fourth is stored and marked rate-limited so both of you can see it happened, but the owner is not told. An alert or a banner on an install that never granted alerts:write is stored as rejected for the same reason. Events are not counted against the alert limit.

Replaying

An owner can re-run a delivery from the extension’s page in their workspace, and you can re-run one from your Runtime page. A replay re-runs the outcome against the stored payload; it does not create a second delivery and does not call your server. Replaying an alert spends the project’s allowance for that day again, and is refused with ALERT_LIMIT_REACHED when the day is already spent.