Webhooks
Subscribe to events like balance changes, user connections, and usage completions. Quota delivers webhook payloads to your HTTPS endpoint and signs every request so you can verify authenticity.
Events
| Event | Description |
|---|---|
user.connected | A user linked their Quota account to your app via OAuth |
user.disconnected | A user unlinked their account |
balance.updated | A user's credit balance changed |
balance.low | A user's balance dropped below the configured threshold |
usage.completed | An API request completed and credits were deducted |
Event Payloads
Every webhook delivery sends a JSON body with an id, type, created_at, and event-specific data. Below are examples for each event type.
balance.updated
Sent when a user's credit balance changes after an API request.
{
"id": "evt_abc123",
"type": "balance.updated",
"created_at": "2026-01-15T12:00:00.000Z",
"data": {
"user_id": "usr_123",
"new_balance": 450,
"amount_spent": 50,
"model": "gpt-4o",
"endpoint": "/v1/chat/completions"
}
}balance.low
Sent when a user's balance drops below the configured low_balance_threshold.
{
"id": "evt_def456",
"type": "balance.low",
"created_at": "2026-01-15T12:05:00.000Z",
"data": {
"user_id": "usr_123",
"current_balance": 45,
"threshold": 50
}
}usage.completed
Sent after each API request completes and credits are deducted.
{
"id": "evt_ghi789",
"type": "usage.completed",
"created_at": "2026-01-15T12:00:01.000Z",
"data": {
"user_id": "usr_123",
"model": "gpt-4o",
"prompt_tokens": 150,
"completion_tokens": 80,
"total_tokens": 230,
"credits_used": 50
}
}user.connected
Sent when a user links their Quota account to your app via OAuth.
{
"id": "evt_jkl012",
"type": "user.connected",
"created_at": "2026-01-15T10:00:00.000Z",
"data": {
"user_id": "usr_123",
"email": "user@example.com"
}
}user.disconnected
Sent when a user unlinks their account from your app.
{
"id": "evt_mno345",
"type": "user.disconnected",
"created_at": "2026-01-15T14:00:00.000Z",
"data": {
"user_id": "usr_123",
"email": "user@example.com"
}
}Signature Verification
Every delivery includes an X-Quota-Signature header containing an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
import crypto from "crypto";
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}The Core SDK provides verifyWebhookSignature (using Web Crypto, works on edge runtimes), and the Next.js SDK adds createWebhookHandler for automatic route handling.
Headers
| Header | Description |
|---|---|
X-Quota-Signature | HMAC-SHA256 hex digest of the raw body |
X-Quota-Event | The event type (e.g. balance.updated) |
Content-Type | application/json |
User-Agent | Quota-Webhooks/1.0 |
Endpoints
POST /v1/webhooks
Create a new webhook subscription.
curl -X POST https://api.usequota.ai/v1/webhooks \
-H "Authorization: Bearer sk-quota-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"client_id": "your_client_id",
"url": "https://yourapp.com/api/webhooks/quota",
"events": ["balance.updated", "balance.low", "usage.completed"],
"low_balance_threshold": 50
}'Response (201):
{
"id": "wh_abc123",
"url": "https://yourapp.com/api/webhooks/quota",
"events": ["balance.updated", "balance.low", "usage.completed"],
"secret": "whsec_abc123...",
"low_balance_threshold": 50,
"active": true,
"created_at": "2026-01-15T12:00:00.000Z"
}Important: The secret is only returned on creation. Store it securely for signature verification.
GET /v1/webhooks
List all webhooks for a client.
curl https://api.usequota.ai/v1/webhooks?client_id=your_client_id \
-H "Authorization: Bearer sk-quota-your-api-key"GET /v1/webhooks/:id
Get a single webhook's configuration.
PATCH /v1/webhooks/:id
Update a webhook's URL, events, threshold, or active status.
curl -X PATCH https://api.usequota.ai/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer sk-quota-your-api-key" \
-H "Content-Type: application/json" \
-d '{"active": false}'DELETE /v1/webhooks/:id
Delete a webhook subscription.
GET /v1/webhooks/:id/deliveries
View recent delivery attempts for a webhook, including response status and body.
{
"deliveries": [
{
"id": "del_xyz",
"event_type": "balance.updated",
"payload": { "..." : "..." },
"response_status": 200,
"response_body": "OK",
"delivered_at": "2026-01-15T12:00:01.000Z",
"created_at": "2026-01-15T12:00:00.000Z"
}
]
}Retries
Failed deliveries (non-2xx responses or network errors) are retried up to 3 times within 24 hours of creation.
URL Requirements
- Production webhooks must use
https:// http://is allowed forlocalhostduring development