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
| Event | Fires when |
|---|---|
transfer.created | A token transfer involving an address or token you watch lands on chain. |
balance.changed | An account/token pair you watch has its balance change. |
token.launched | A new token gets registered in TokenKit. |
price.threshold | A 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
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:
| Header | What it is |
|---|---|
X-Tokenkit-Event | The event name, e.g. transfer.created |
X-Tokenkit-Delivery | Unique delivery ID (use this to dedupe) |
X-Tokenkit-Signature | sha256=<hex> HMAC of the request body, keyed by your webhook secret |
X-Tokenkit-Attempt | Which retry this is (1 = first try) |
The body is 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:
1import hmac, hashlib2 3def is_valid(body: bytes, header: str, secret: str) -> bool:4 if not header.startswith("sha256="):5 return False6 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.
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:
1curl -X POST 'https://api.tokenkithq.io/api/webhooks/42/deliveries/9876/retry/' \2 -H 'Authorization: Bearer YOUR_SESSION_TOKEN'Other endpoints
| Path | Method | What it does |
|---|---|---|
/api/webhooks/ | GET | List your webhooks |
/api/webhooks/ | POST | Create a webhook (see above) |
/api/webhooks/{id}/ | GET | Single webhook + its subscriptions |
/api/webhooks/{id}/ | PATCH | Update url/events/status/subscriptions. Sending a subscriptions array replaces the set. |
/api/webhooks/{id}/ | DELETE | Permanently delete a webhook and its delivery history |
/api/webhooks/{id}/test/ | POST | Send a synthetic ping so you can confirm wiring without waiting for a real event |
/api/webhooks/{id}/rotate-secret/ | POST | Generate 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-Deliveryas 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
failedordeaddeliveries, 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:
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:
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.