Webhooks
Get notified in real time when users cross spending thresholds, hit gates, or on a schedule — via HTTP POST to your endpoint.
Paygent can POST event data to a URL you configure whenever something meaningful happens. Use webhooks to trigger Slack alerts, send upgrade emails, reconcile billing, or feed your own alerting system — without polling.
Event types
| Event type | Trigger | What it means |
|---|---|---|
gate_event.soft_gate |
User hit a soft gate | Warning threshold (default 80%) crossed |
gate_event.hard_gate |
User hit a hard gate | Limit reached, call was checked |
gate_event.blocked |
Call was blocked | Hard gate fired, no tokens consumed |
usage.threshold.50 |
User crossed 50% of plan spend limit | Halfway through their budget |
usage.threshold.80 |
User crossed 80% of plan spend limit | Approaching limit |
usage.threshold.100 |
User crossed 100% of plan spend limit | Over limit |
usage.summary.daily |
Scheduled (6 AM UTC daily) | Daily usage rollup for your product |
usage.summary.weekly |
Scheduled (Monday 6 AM UTC) | Weekly usage rollup |
usage.summary.monthly |
Scheduled (1st of month 6 AM UTC) | Monthly usage rollup |
Threshold events (usage.threshold.*) fire once per user per billing period — you won't get spammed if a user hovers around the 80% mark.
Create a webhook endpoint
Via API
curl -X POST https://api.paygent.to/api/v1/webhooks/endpoints \
-H "Authorization: Bearer pg_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/paygent",
"subscribed_events": ["gate_event.hard_gate", "usage.threshold.80", "usage.threshold.100"],
"description": "Production alerting"
}'
Response (201):
{
"id": "550e8400-...",
"url": "https://your-app.com/webhooks/paygent",
"secret": "whsec_a1b2c3d4...",
"subscribed_events": ["gate_event.hard_gate", "usage.threshold.80", "usage.threshold.100"],
"description": "Production alerting",
"enabled": true
}
Save the secret — it's shown only on creation. You'll use it to verify webhook signatures. If you lose it, rotate it via POST /webhooks/endpoints/{id}/rotate-secret.
Via Dashboard
- Go to app.paygent.to → Webhooks
- Click Create Endpoint
- Enter your URL and select the event types you want
- Copy the signing secret
Payload format
Every webhook POST has this envelope:
{
"id": "whevt_550e8400-...",
"type": "gate_event.hard_gate",
"api_version": "2026-06-22",
"created_at": "2026-06-22T14:30:00Z",
"product_id": "...",
"data": { ... }
}
Headers
Content-Type: application/json
User-Agent: Paygent-Webhooks/1.0
X-Paygent-Event: gate_event.hard_gate
X-Paygent-Delivery: whevt_550e8400-...
X-Paygent-Signature: sha256=<hex>
X-Paygent-Timestamp: 1719063000
Verifying signatures
Paygent signs every webhook with HMAC-SHA256. Verify it to ensure the request came from Paygent:
import hmac
import hashlib
def verify_signature(payload_body: bytes, signature_header: str, timestamp_header: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
f"{timestamp_header}.{payload_body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature_header)
Gate event payload (gate_event.*)
{
"data": {
"user_id": "user_123",
"status": "hard_gate",
"gate_reason": "total_spend",
"usage_pct": 1.05,
"current_value": 10.50,
"limit_value": 10.00,
"blocked": true,
"message": "Spend limit reached"
}
}
Threshold payload (usage.threshold.*)
{
"data": {
"user_id": "user_123",
"threshold_pct": 80,
"current_spend": 8.05,
"spend_limit": 10.00,
"plan_name": "pro",
"tokens_total": 145200,
"cost_by_model": {"gpt-4o": 5.20, "claude-sonnet": 2.85}
}
}
Summary payload (usage.summary.*)
{
"data": {
"summary_type": "daily",
"period_start": "2026-06-21T00:00:00Z",
"period_end": "2026-06-22T00:00:00Z",
"active_users": 42,
"total_cost": 127.45,
"total_tokens": 2340000,
"total_events": 1823,
"soft_gates": 12,
"hard_gates": 3,
"top_users": [{"user_id": "user_123", "cost": 15.20, "tokens": 280000}]
}
}
Retry behavior
If your endpoint returns a non-2xx status or times out (10s), Paygent retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 8 hours |
After 6 failed attempts (~10.5 hours total), the delivery is marked as failed. You can inspect failed deliveries in the dashboard or via the API.
Managing endpoints
| Action | API | Dashboard |
|---|---|---|
| List endpoints | GET /webhooks/endpoints |
Webhooks page |
| Update URL or events | PATCH /webhooks/endpoints/{id} |
Edit button |
| Disable/enable | PATCH /webhooks/endpoints/{id} with enabled: false/true |
Toggle |
| Rotate secret | POST /webhooks/endpoints/{id}/rotate-secret |
Rotate button |
| View delivery log | GET /webhooks/endpoints/{id}/deliveries |
Deliveries tab |
| Send test event | POST /webhooks/endpoints/{id}/test |
Test button |
| Delete | DELETE /webhooks/endpoints/{id} |
Delete button |
Common patterns
Slack alert on hard gate
Subscribe to gate_event.hard_gate, parse the data.user_id and data.message, and POST a formatted message to a Slack incoming webhook.
Upgrade email on 80% threshold
Subscribe to usage.threshold.80, look up the user's email in your system, and send an email prompting them to upgrade.
Daily cost Slack digest
Subscribe to usage.summary.daily, format the top users and total cost, and post to a #cost-monitoring channel.
Billing reconciliation
Subscribe to usage.threshold.100, and when a user crosses 100%, call your billing system to charge overage or suspend access.
What's next
- Backend API → Webhooks — full endpoint reference for webhook CRUD and delivery queries
- Callbacks & Events — in-process callbacks (complementary to webhooks)
- Dashboard — view webhook endpoints and delivery logs visually