Webhooks
1tokens sends a signed POST request to the HTTPS URL you register in the console whenever a subscribed event happens. Create an endpoint on the Webhooks page and tick the events you want; new endpoints take effect within 30 seconds.
Event catalog
Model
| Type | Name | data fields |
|---|---|---|
model.listed | Model listed | model tenant_id previous_status status actor |
model.delisted | Model delisted | model tenant_id previous_status status actor |
model.deprecated | Model deprecated | model previous_status status actor |
model.price.changed | Price changed | model tenant_id pricing_status sale_price_mode multiplier price changes actor |
model.user_price.changed | User price changed | model user_id multiplier changes actor |
model.updated | Model updated | model changes actor |
API Key
| Type | Name | data fields |
|---|---|---|
api_key.created | Key created | api_key actor |
api_key.updated | Key updated | api_key changes actor |
api_key.revoked | Key revoked | api_key previous_status status actor |
api_key.expired | Key expired | api_key actor |
api_key.quota.exhausted | Quota exhausted | api_key quota_limit_usd consumed_usd actor |
api_key.rate_window.exceeded | Window limit exceeded | api_key window limit_usd consumed_usd actor |
Account
| Type | Name | data fields |
|---|---|---|
account.balance.low | Low balance | account threshold_usd request_id actor |
account.balance.exhausted | Balance exhausted | account reason request_id actor |
account.credit.topup | Top-up | account amount_usd amount_credits source order_no actor |
account.credit.adjusted | Credit adjusted | account delta_usd delta_credits kind reason actor |
account.trial.granted | Trial granted | account amount_credits actor |
account.disabled | Account disabled | account previous_status status actor |
account.enabled | Account enabled | account previous_status status actor |
account.password.reset | Password reset | account actor |
Order
| Type | Name | data fields |
|---|---|---|
order.created | Order created | order actor |
order.paid | Order paid | order paid_at previous_status status actor |
order.failed | Payment failed | order reason previous_status status actor |
order.redeemed | Code redeemed | order code_masked actor |
Subscription
| Type | Name | data fields |
|---|---|---|
subscription.started | Subscription started | subscription previous_status status actor |
subscription.renewed | Subscription renewed | subscription actor |
subscription.upgraded | Subscription upgraded | subscription changes actor |
subscription.cancelled | Subscription cancelled | subscription previous_status status actor |
subscription.payment_failed | Renewal failed | subscription reason actor |
subscription.quota.exhausted | Quota exhausted | subscription request_id actor |
Task
| Type | Name | data fields |
|---|---|---|
task.video.succeeded | Video done | task video_count actor |
task.video.failed | Video failed | task actor |
task.generation.succeeded | Generation done | task media output_count actor |
task.generation.failed | Generation failed | task media actor |
Content
| Type | Name | data fields |
|---|---|---|
content.changelog.published | Changelog published | entry actor |
content.article.published | Article published | article tenant_id previous_status status actor |
content.article.unpublished | Article unpublished | article tenant_id previous_status status actor |
Webhook
| Type | Name | data fields |
|---|---|---|
webhook.endpoint.auto_disabled | Endpoint auto-disabled | endpoint reason consecutive_failures actor |
Payload format
Every event shares one envelope: id (unique event id), type, domain, created_at (ISO 8601 UTC), api_version and data. Inside data keys are snake_case, timestamps end with _at, amounts are decimal strings (_usd / _credits) and absent values are explicit null. The same event may be delivered more than once because of retries, so deduplicate by id.
{ "id": "evt_01JBXK6Q3N7Y8R2V4W5X6Z7A8B", "type": "model.price.changed", "domain": "model", "created_at": "2026-11-04T08:12:33Z", "api_version": "2026-11-04", "data": { }}Request headers
POST https://your-server.example/webhooksContent-Type: application/jsonUser-Agent: Webhooks/1.0X-Event-Id: evt_01JBXK6Q3N7Y8R2V4W5X6Z7A8BX-Event-Type: model.price.changedX-Delivery-Id: 123456X-Timestamp: 1762243953X-Signature: v1=5f1c…Verifying the signature
Compute HMAC-SHA256 with the secret shown when the endpoint was created over the string X-Timestamp value + . + raw request body, and compare it in constant time with the hex after v1= in X-Signature. Reject requests whose timestamp differs from now by more than 5 minutes. Respond 2xx first and process afterwards; the whole response must finish within 10 seconds.
import { createHmac, timingSafeEqual } from "node:crypto";export function verify(secret, headers, rawBody) { const ts = headers["x-timestamp"]; if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; const expected = createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex"); const given = String(headers["x-signature"]).replace(/^v1=/, ""); return given.length === expected.length && timingSafeEqual(Buffer.from(given, "hex"), Buffer.from(expected, "hex"));}import hmac, hashlib, timedef verify(secret: str, headers: dict, raw_body: bytes) -> bool: ts = headers["X-Timestamp"] if abs(time.time() - int(ts)) > 300: return False expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest() given = headers["X-Signature"].removeprefix("v1=") return hmac.compare_digest(given, expected)func verify(secret string, ts, sig string, body []byte) bool { t, _ := strconv.ParseInt(ts, 10, 64) if math.Abs(float64(time.Now().Unix()-t)) > 300 { return false } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(ts + ".")) mac.Write(body) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(strings.TrimPrefix(sig, "v1=")), []byte(expected))}Retries and auto-disable
A non-2xx response or no response within 10 seconds counts as a failure. Each delivery is attempted up to 7 times with the delays below; after that it is marked exhausted. An endpoint whose 10 consecutive deliveries are exhausted is disabled automatically and you are notified by email; re-enable it in the console once fixed. Any delivery can be re-sent manually from the console.
| Attempt | Delay after previous |
|---|---|
| 1 | 0 |
| 2 | 1 min |
| 3 | 5 min |
| 4 | 30 min |
| 5 | 2 h |
| 6 | 6 h |
| 7 | 24 h |
Endpoint requirements
The URL must be a publicly reachable HTTPS address; hostnames that resolve to private, loopback or link-local ranges are rejected. Redirects are not followed and at most 64 KB of the response body is read.

