TAGBASE

API reference

Webhooks

Get notified when things change. Signed, so you can trust them.

Most of the API is request/response: you call, you read the result. Webhooks are the exception, the one place the platform calls you. When something happens to one of your tags, we POST an event to a URL you’ve registered, so your integration learns about changes it didn’t initiate (most importantly, a tag being written to a chip out in the field).

Every delivery is signed. The signature is what makes a webhook trustworthy, so the rule is simple: if a request isn’t validly signed, reject it. Never act on an unsigned or mis-signed request.

Registering a webhook

Webhooks are configured per team in the back office. When you add one, a signing secret (whsec_…) is generated and shown once. Store it; it’s the key you’ll verify deliveries with. A team can have several webhooks; each has its own secret and can be disabled or deleted.

A webhook receives events only for its own team’s tags. Register it on the team whose tags you want to hear about.

Events

Each event has a stable type. The catalog today:

Type When
tag.created A tag is provisioned under your team.
tag.configured A tag is written to a chip and reaches configured, ready to scan.
tag.configuration_failed A configuration attempt failed; the tag stays unconfigured.

More types may be added over time. Treat unknown types as a no-op rather than erroring, so new events never break your receiver.

Payload

Deliveries are POSTed as application/vnd.api+json, in the same JSON:API shape as the rest of the API. The affected resource is the top-level data; the event envelope (id, type, timestamp) lives in meta.event:

{
"data": {
"type": "tags",
"id": "tag_abcdef0123456789",
"attributes": {
"url": "https://zannatherapeutics.com/verify/lonafen/8a3f9c2b",
"status": "configured",
"protocol": "ntag_424_dna"
}
},
"meta": {
"event": {
"id": "evt_9f8c2b1a4d6e0f3a72",
"type": "tag.configured",
"created_at": "2026-06-28T12:30:00.000000Z"
}
}
}

The example above is a tag.configured delivery. Every event uses the same shape: only data.attributes.status and meta.event.type differ. A tag.created delivery looks like:

{
"data": {
"type": "tags",
"id": "tag_abcdef0123456789",
"attributes": {
"url": "https://zannatherapeutics.com/verify/lonafen/8a3f9c2b",
"status": "created",
"protocol": "ntag_424_dna"
}
},
"meta": {
"event": {
"id": "evt_1b2c3d4e5f6a7b8c90",
"type": "tag.created",
"created_at": "2026-06-28T12:00:00.000000Z"
}
}
}

Dispatch on meta.event.type. meta.event.id is the event’s unique id. Use it to deduplicate (see Delivery & retries). For tag.configuration_failed, meta.event also carries a reason.

Verifying the signature

Every request carries a Tagbase-Hmac-SHA256 header, the Base64-encoded HMAC-SHA256 of the raw request body, keyed by your webhook’s signing secret:

Tagbase-Hmac-SHA256: 5dPp1Lq8w4l8m2p0r3s5t7v9x1z3B5D7F9H1J3L5N7P=

To verify, recompute the HMAC over the raw request body (the exact bytes: don’t re-serialize the parsed JSON, or whitespace/key-order differences will break the check), Base64-encode it, and constant-time compare it to the header.

defmodule Receiver do
def valid?(raw_body, presented, secret) do
expected =
:hmac
|> :crypto.mac(:sha256, secret, raw_body)
|> Base.encode64()
Plug.Crypto.secure_compare(expected, presented)
end
end

The signature covers the body only; use the event id (next section) to ignore anything you’ve already processed.

Respond 2xx once you’ve accepted the event. Any other status (or a timeout) is treated as a failure and retried.

Delivery and retries

  • At-least-once. A delivery that doesn’t get a 2xx is retried with exponential backoff. The same event may therefore arrive more than once: dedupe on the event id and make handling idempotent.
  • Order isn’t guaranteed. Don’t assume events arrive in the order they occurred; use created_at if you need to reason about timing.
  • Disabling. Disable a webhook in the back office to pause deliveries without losing its configuration; delete it to stop permanently.

Security checklist

  • Reject any request without a valid Tagbase-Hmac-SHA256. This is the only proof the request came from us.
  • Compare signatures in constant time.
  • Dedupe on the event id so a replayed delivery is a no-op.
  • Keep your signing secret server-side; rotate it (delete + re-add the webhook) if it’s ever exposed.