Documentation Index
Fetch the complete documentation index at: https://docs.scute.io/llms.txt
Use this file to discover all available pages before exploring further.
Webhooks
Receive real-time notifications when events happen in your app. Signed with HMAC-SHA256.
Setup
POST /v1/apps/{app_id}/webhooks
Authorization: Bearer {api_key}
{
"webhook": {
"url": "https://your-server.com/webhooks",
"event_types": ["verification.sms.verified", "verification.sms.expired"],
"retry_limit": 3
}
}
| Parameter | Type | Required | Description |
|---|
url | string | yes | HTTPS endpoint |
event_types | string[] | no | Events to subscribe to. Default: all |
retry_limit | int | no | Retry attempts (0-10). Default: 3 |
description | string | no | Label |
metadata | object | no | Custom metadata |
enabled | bool | no | Default: true |
Payload
Content-Type: application/json
User-Agent: AppName-Webhooks/1.0
X-Webhook-Signature: t=1713232500,v1=5a3c2b...
{
"id": "uuid",
"created_at": "2026-04-16T12:00:00Z",
"event_type": "verification.sms.verified",
"app_id": "uuid",
"data": { ... },
"api_version": "v1"
}
Signature verification
The X-Webhook-Signature header contains a timestamp and HMAC:
t=1713232500,v1=5a3c2b...
Verify:
const crypto = require("crypto");
function verifyWebhook(payload, header, secret) {
const [tPart, sigPart] = header.split(",");
const timestamp = tPart.replace("t=", "");
const signature = sigPart.replace("v1=", "");
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${payload}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Events
Verification events
Pattern: verification.{channel}.{outcome} — channel is email or sms.
| Event | When |
|---|
verification.*.requested | Verification sent |
verification.*.verified | Contact confirmed (magic link click, OTP code, or consent approval) |
verification.*.expired | Timed out. Fires even if the link was never opened |
verification.*.failed | OTP max attempts exceeded |
verification.*.denied | Contact denied the action (consent mode) |
verification.*.delivery_failed | SMS/email failed to send (bad number, provider error) |
Verification payloads include:
{
"data": {
"verification_id": "uuid",
"intent": "Password Reset",
"meta_data": {
"contact_email": "user@company.com",
"ticket_id": "12345",
"delivery_method": "sms"
},
"timing": {
"sent_at": "2026-04-16T01:00:00Z",
"opened_at": "2026-04-16T01:00:47Z",
"confirmed_at": "2026-04-16T01:00:52Z",
"time_to_verify_seconds": 52
},
"device": { "os": "iOS", "browser": "Safari" },
"ip_address": "203.0.113.42"
}
}
Expiration events add was_opened: true/false. Consent events add consent_decision and denial_reason. Delivery failures add error and channel.
Auth events
| Event | When |
|---|
auth.login.failed | Login attempt failed |
challenge.created | Challenge created (any type) |
challenge.completed | Challenge verified |
challenge.failed | Challenge failed |
User events
| Event | When |
|---|
user.created | New user registered |
user.updated | User profile updated |
user.identifier_changed | Email or phone changed |
System events
| Event | When |
|---|
webhook.test | Test ping from dashboard |
Managing endpoints
| Action | Method |
|---|
| List | GET /v1/apps/{app_id}/webhooks |
| Get | GET /v1/apps/{app_id}/webhooks/{id} |
| Update | PATCH /v1/apps/{app_id}/webhooks/{id} |
| Delete | DELETE /v1/apps/{app_id}/webhooks/{id} |
| Test | POST /v1/apps/{app_id}/webhooks/{id}/test |
| Deliveries | GET /v1/apps/{app_id}/webhooks/{id}/deliveries |
Delivery status
| Status | Meaning |
|---|
pending | Queued |
processing | In flight |
delivered | 2xx response |
failed | Will retry if retryable |
Limits
- 10-second timeout per delivery
- Max 10 retries with exponential backoff
- Respond with 2xx within 5 seconds
- 4xx = won’t retry. 5xx = will retry.