Agent skill
Axilio Webhooks Skill
Receive and verify Axilio webhooks (real-phone automation), signed with the Standard Webhooks scheme. Use when setting up an Axilio webhook destination, getting the whsec_ signing secret, debugging webhook-id / webhook-timestamp / webhook-signature verification, or handling events like run.completed, run.failed, session.ended, balance.low, invoice.payment_failed, file.ready and phone.offline.
Install this skill
npx skills add hookdeck/webhook-skills --skill axilio-webhooks
Axilio automates real phones: workflow runs, live sessions, files, billing and phone rentals. Its webhooks tell your server when a run finishes, a session ends, a payment fails or a rented phone goes offline, instead of polling. Every delivery is signed with the Standard Webhooks scheme (webhook-id, webhook-timestamp, webhook-signature).
When to Use This Skill
- How do I receive Axilio webhooks?
- How do I verify an Axilio webhook signature?
- Where do I find my Axilio
whsec_signing secret? - How do I handle
run.completed,run.failedorsession.endedevents? - Why is my Axilio
webhook-signatureverification failing? - How do I dedupe Axilio webhook retries?
Verification (core)
| Header | Value |
|---|---|
webhook-id | Unique per event, the same as the body's id, stable across retries |
webhook-timestamp | Unix seconds of this delivery attempt |
webhook-signature | v1,<base64 HMAC-SHA256>, possibly several space-separated entries |
Signed content is {webhook-id}.{webhook-timestamp}.{raw body}. The key is the base64-decoded part of the destination's secret after whsec_. Each destination has its own secret, shown once when you register it. Axilio has no SDK of its own; the examples use standardwebhooks (npm, PyPI), which compares in constant time and tries every v1 entry.
const { Webhook } = require('standardwebhooks');
// rawBody: exact request body string, NOT re-serialised JSON
function verifyAxilioWebhook(rawBody, headers, secret = process.env.AXILIO_WEBHOOK_SECRET) {
return new Webhook(secret).verify(rawBody, {
'webhook-id': headers['webhook-id'],
'webhook-timestamp': headers['webhook-timestamp'],
'webhook-signature': headers['webhook-signature'],
}); // returns the parsed event; throws → respond 400
}
Python: from standardwebhooks.webhooks import Webhook, then Webhook(secret).verify(await request.body(), {...same three headers...}). The examples also read the svix-id, svix-timestamp and svix-signature headers when webhook-id is absent, as Hookdeck does.
Hookdeck retries and the 5-minute window. The library always rejects a
webhook-timestampmore than 5 minutes old. Hookdeck retries and replays resend the original timestamp, so the library rejects late retries after 5 minutes. A dependency-free verifier where the window can be dropped or widened is in references/verification.md.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Envelope
{ "id": "run.completed:0199a3c2-58c1-7f3a-9f2e-2f9a6a2f6a11", "type": "run.completed",
"occurred_at": "2026-09-24T12:00:45Z", "test": false,
"data": { "id": "...", "workflow_id": "...", "status": "completed", "success": true } }
id is <type>:<resource id>[:<time>] and equals webhook-id. test is true only for dashboard tests. data uses API field names and carries ids, statuses, reasons and timestamps, never your content (variables, files, recordings, download links). Fetch those through the API.
Common Event Types
The docs list 28 events (the list may not be exhaustive).
| Area | Events |
|---|---|
| Runs | run.started, run.completed, run.failed, run.cancelled |
| Sessions | session.started, session.ended, session.recording_ready |
| Billing | balance.low, balance.depleted, balance.recovered, invoice.paid, invoice.payment_failed, subscription.updated, subscription.canceled, auto_recharge.succeeded, auto_recharge.failed |
| Files | file.ready, file.capture_failed, file.delivered, file.delivery_failed |
| Phones (rented only) | phone.maintenance_, phone.maintenance_ended, phone.offline, phone.online, phone.rental.activated, phone.rental.past_due, phone.rental.canceled |
| Platform | webhook.test |
Full payloads: references/overview.md and Axilio's event reference.
Delivery Semantics
- At least once. Return any
2xxquickly, then process asynchronously. Any other response, or none, is retried after 30 s, 2 min, 5 min, 15 min and 1 h. - Duplicates are possible. Skip an
idyou have already handled. - Order is not guaranteed.
run.completedcan arrive beforerun.started. Compareoccurred_ator fetch the resource. - Failed deliveries can be replayed from the dashboard's delivery log.
Environment Variables
AXILIO_WEBHOOK_SECRET=whsec_xxxxx # the destination's full signing secret; handlers fail closed (500) without it
Copy it when you register the destination under Settings > Webhooks. It is shown once.
Local Development
# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 axilio --path /webhooks/axilio
Register the printed URL under Settings > Webhooks > Register Destination, then use Test on the destination to send an example event.
Reference Materials
- references/overview.md - Event catalog, payload fields, duplicate ids, retries
- references/setup.md - Registering a destination, signing secret, testing
- references/verification.md - Library and manual verification, gotchas, debugging