Webhooks — subscribe to thing-state notifications

Once you have registered and can sign requests (see Registration), you can subscribe to thing-state notifications instead of only polling. A webhook subscription asks trustmint to POST each new state-change to an https:// endpoint you host.

⚠️ Webhooks do NOT replace polling ⚠️

A subscription is a latency optimization layered on top of the GET /api/v1/state-changes polling contract — it does not replace it. Webhook delivery is best-effort; the durable source of truth is the cursor-paged feed. You must keep polling. trustmint enforces this: a subscription request that does not acknowledge the polling requirement is rejected (THM-60110).

Step 1 — create the subscription (AK/SK-signed)

POST /api/v1/state-change-subscriptions
X-FGAI-Access-Key-Id: <your-access-key-id>
X-FGAI-Signature: <hex-hmac>
X-FGAI-Timestamp: <rfc3339-utc>
Content-Type: application/json

{
  "target_url": "https://your-host.example/hooks/thing-state",
  "acknowledge_polling_still_mandatory": true,
  "event_filter": {
    "new_states": ["ACTIVATED", "DEACTIVATED"],
    "thing_ids":  ["thing-..."]
  }
}

Three rules the request body must satisfy (each backed by a subscriptions error code in bucket 6 of the error catalog):

Rule

If violated

acknowledge_polling_still_mandatory must be true

THM-60110 — subscribing does not replace the mandatory GET /state-changes polling contract

target_url must be https://

THM-60120target_url must use https://

event_filter accepts only optional new_states + thing_ids string arrays

THM-60140event_filter must contain only those two arrays

A SUSPENDED subscription cannot be edited except to PATCH status=ACTIVE (THM-60150), and an unknown subscription id returns THM-60100.

Step 2 — verify the HMAC on every delivered event

Each delivered POST to your target_url is HMAC-signed with your webhook-secret (a secret distinct from your AK/SK secret). Your receiver must recompute and compare the HMAC before trusting the payload — an unsigned or wrong-signature delivery must be rejected, because an attacker who learns your target_url could otherwise spoof state-changes.

The Go/Python 04-verify-webhook example hosts a receiver and calls the SDK verify helper for you — see SDK quickstart. If a delivery fails verification, do not act on it; rely on the polling feed (Step 3) for the authoritative state.

Step 3 — keep polling GET /api/v1/state-changes

Webhooks can be dropped (your endpoint is down, a redeploy, a network blip). The cursor-paged feed is the durable backstop:

GET /api/v1/state-changes?since=<next_cursor>
X-FGAI-Access-Key-Id: <your-access-key-id>
X-FGAI-Signature: <hex-hmac>
X-FGAI-Timestamp: <rfc3339-utc>

Feed errors live in bucket 5 (state-changes-feed) of the error catalog:

Code

Meaning

THM-50100

?registrar_id= is rejected — the registrar is derived from your authenticated identity

THM-50110

cursor malformed — resume from a prior next_cursor or restart without one

THM-50120

cursor older than 72h — perform a full sync via GET /api/v1/things and resume

Note. Do not pass ?registrar_id= — trustmint derives the registrar from your signed identity and rejects the parameter (THM-50100).

The bucket-5 + bucket-6 tables (full titles + descriptions) are sourced from the generated thingmake-error-catalog.md — see the error catalog.

What next