Webhooks

Get pinged when transfers happen, balances change, tokens launch, or prices cross your thresholds

Webhooks let TokenKit call your URL whenever something happens on Starknet that you care about. Instead of polling the API, you tell us what to watch and where to send the news, and we POST you a signed payload as soon as it happens.

What you can subscribe to

EventFires when
transfer.createdA token transfer involving an address or token you watch lands on chain.
balance.changedAn account/token pair you watch has its balance change.
token.launchedA new token gets registered in TokenKit.
price.thresholdA token's price crosses an above/below threshold you set.

Create a webhook

POST /api/webhooks/. Authenticated with your bearer session token (the one you got from POST /api/users/auth/login/).

Each webhook has one or more subscriptions. A subscription is a (token, address) filter where either field is optional. Empty means "any":

  • (token: "0x053...", address: "") -> every transfer of that token
  • (token: "", address: "0x07...") -> every transfer involving that address
  • (token: "0x053...", address: "0x07...") -> only this token + this address
Code
bash
1curl -X POST 'https://api.tokenkithq.io/api/webhooks/' \
2 -H 'Authorization: Bearer YOUR_SESSION_TOKEN' \
3 -H 'Content-Type: application/json' \
4 -d '{
5 "name": "treasury watcher",
6 "url": "https://my-app.com/hooks/tokenkit",
7 "events": ["transfer.created", "balance.changed"],
8 "subscriptions": [
9 { "token": "", "address": "0x0729b73..." },
10 { "token": "0x053c91...", "address": "" }
11 ]
12 }'

The response includes a secret. Save it. We use it to sign every delivery, and you use it to verify deliveries are really from us.

What a delivery looks like

Every POST we send to your URL has these headers:

HeaderWhat it is
X-Tokenkit-EventThe event name, e.g. transfer.created
X-Tokenkit-DeliveryUnique delivery ID (use this to dedupe)
X-Tokenkit-Signaturesha256=<hex> HMAC of the request body, keyed by your webhook secret
X-Tokenkit-AttemptWhich retry this is (1 = first try)

The body is JSON:

Code
json
1{
2 "event": "transfer.created",
3 "created_at": "2026-05-10T17:32:14.123Z",
4 "data": {
5 "token_address": "0x053c91253...",
6 "from_address": "0x0729b73...",
7 "to_address": "0x05a1f4...",
8 "value": "1000000000",
9 "txhash": "0x021ab...",
10 "block": 123456,
11 "timestamp": 1731264734,
12 "fee": "1234567",
13 "fee_unit": "fri",
14 "event_type": "Transfer"
15 }
16}

Verifying a delivery

Recompute the HMAC on your end and compare. Any HMAC-SHA256 library will do:

Code
python
1import hmac, hashlib
2 
3def is_valid(body: bytes, header: str, secret: str) -> bool:
4 if not header.startswith("sha256="):
5 return False
6 expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
7 return hmac.compare_digest(header[7:], expected)

Reject any request whose signature does not match. If it does match, you know the body has not been tampered with and that it really came from TokenKit.

Retries

If your endpoint returns anything other than a 2xx (or times out after 10 seconds), we retry with exponential backoff: 30 seconds, 5 minutes, 30 minutes, 2 hours, 12 hours. After the fifth failure the delivery is marked dead and we stop trying.

If a webhook racks up 10 dead deliveries in a row, we automatically pause it (status -> disabled) so we do not keep hammering a broken endpoint. You can re-enable it from the UI or with PATCH /api/webhooks/{id}/.

Listing past deliveries

GET /api/webhooks/{id}/deliveries/. Returns the delivery log (paginated), with status, response code, body, and any error.

Code
bash
1curl 'https://api.tokenkithq.io/api/webhooks/42/deliveries/' \
2 -H 'Authorization: Bearer YOUR_SESSION_TOKEN'

You can manually retry any delivery that is not success:

Code
bash
1curl -X POST 'https://api.tokenkithq.io/api/webhooks/42/deliveries/9876/retry/' \
2 -H 'Authorization: Bearer YOUR_SESSION_TOKEN'

Other endpoints

PathMethodWhat it does
/api/webhooks/GETList your webhooks
/api/webhooks/POSTCreate a webhook (see above)
/api/webhooks/{id}/GETSingle webhook + its subscriptions
/api/webhooks/{id}/PATCHUpdate url/events/status/subscriptions. Sending a subscriptions array replaces the set.
/api/webhooks/{id}/DELETEPermanently delete a webhook and its delivery history
/api/webhooks/{id}/test/POSTSend a synthetic ping so you can confirm wiring without waiting for a real event
/api/webhooks/{id}/rotate-secret/POSTGenerate a new signing secret. Old secret stops working immediately.

Tips

  • Make your endpoint idempotent. We retry on failure, and a successful delivery that times out on the response will be redelivered. Use X-Tokenkit-Delivery as a dedupe key.
  • Return fast. Acknowledge the request with a 2xx as soon as you have it queued. Do the heavy work asynchronously.
  • Watch your delivery log. If your status shows lots of failed or dead deliveries, something on your side is broken. The response body we capture should tell you what.

A note on price.threshold

Price-threshold subscriptions use price_above and price_below on the subscription. Either is optional, both can be set on the same subscription:

Code
json
1{
2 "token": "0x053c91253...",
3 "address": "",
4 "price_above": "0.05",
5 "price_below": "0.02"
6}

You get a price.threshold event each time the price crosses one of those lines. A "crossing" means the price moved from one side of the threshold to the other - we do not re-fire while the price stays past the line. The subscription's last known price is saved on every TokenKit price update, so when you change a threshold the new line is compared against the latest snapshot.

The delivery payload tells you what crossed:

Code
json
1{
2 "event": "price.threshold",
3 "created_at": "2026-05-10T17:55:32.123Z",
4 "data": {
5 "token_address": "0x053c91253...",
6 "previous_price": "0.045",
7 "new_price": "0.052",
8 "crossed_above": true,
9 "crossed_below": false,
10 "threshold_above": "0.05",
11 "threshold_below": "0.02"
12 }
13}

If you want both directions on one subscription, set both fields. If you want different webhooks for above vs below (so they can route differently in your system), create two subscriptions with the same token but different price_above / price_below set.

Prices are sourced from TokenKit's price feed, which updates common tokens every few minutes and other tokens whenever something happens. Threshold accuracy is therefore approximate to the price-feed refresh rate, not to the chain itself.