Webhooks fire outgoing HTTP POST requests to your endpoint when platform events occur - sandbox lifecycle changes, deployment completions, billing events, and more. Each delivery is signed with HMAC-SHA256 so you can verify authenticity.
Base path: /api/v1/webhooks
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/v1/webhooks | List all webhooks |
POST | /api/v1/webhooks | Create a webhook |
GET | /api/v1/webhooks/{id} | Get a webhook |
PATCH | /api/v1/webhooks/{id} | Update a webhook |
DELETE | /api/v1/webhooks/{id} | Delete a webhook |
POST | /api/v1/webhooks/{id}/test | Send a test delivery |
GET | /api/v1/webhooks/{id}/deliveries | List delivery attempts |
List Webhooks
GET /api/v1/webhooks
Auth
Authorization: Bearer msk_... Response - 200 OK
{
"data": [
{
"id": "wh_01hwxyz...",
"name": "My Webhook",
"url": "https://api.myapp.com/hooks/miosa",
"events": ["sandbox.created", "sandbox.destroyed"],
"secret_preview": "sk_live_••••••••abcd",
"retry_count": 3,
"created_at": "2026-05-01T00:00:00Z",
"updated_at": "2026-05-01T00:00:00Z"
}
]
} Create a Webhook
POST /api/v1/webhooks
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS endpoint to POST events to |
name | string | No | Human-readable label |
events | string[] | No | Event type filter; empty = all events |
headers | object | No | Extra headers to include in deliveries |
retry_count | integer | No | Number of retries on failure (0-10, default 3) |
metadata | object | No | Arbitrary key-value metadata |
{
"url": "https://api.myapp.com/hooks/miosa",
"name": "Sandbox lifecycle",
"events": ["sandbox.created", "sandbox.ready", "sandbox.error", "sandbox.destroyed"],
"retry_count": 3
} Response - 201 Created
{
"data": {
"id": "wh_01hwxyz...",
"name": "Sandbox lifecycle",
"url": "https://api.myapp.com/hooks/miosa",
"events": ["sandbox.created", "sandbox.ready", "sandbox.error", "sandbox.destroyed"],
"secret": "sk_live_abc123def456...",
"secret_preview": "sk_live_••••••••3456",
"retry_count": 3,
"created_at": "2026-05-26T10:14:23Z",
"updated_at": "2026-05-26T10:14:23Z"
}
} The secret field appears only in this response. Save it securely.
Update a Webhook
PATCH /api/v1/webhooks/{id}
Partial update - include only fields you want to change.
Request Body
Same fields as create, all optional.
Response - 200 OK
Webhook object without the secret field.
Delete a Webhook
DELETE /api/v1/webhooks/{id}
Response - 204 No Content
Send a Test Delivery
POST /api/v1/webhooks/{id}/test
Sends a synthetic webhook.test event to the configured URL to verify connectivity and signature verification.
Response - 200 OK
{
"delivered": true,
"status_code": 200,
"duration_ms": 143
} List Deliveries
GET /api/v1/webhooks/{id}/deliveries
Returns recent delivery attempts with status codes and timing.
Response - 200 OK
{
"data": [
{
"id": "del_01hwyza...",
"event_type": "sandbox.ready",
"status": "delivered",
"status_code": 200,
"attempt": 1,
"duration_ms": 98,
"delivered_at": "2026-05-26T10:14:24Z"
},
{
"id": "del_01hwyzb...",
"event_type": "sandbox.created",
"status": "failed",
"status_code": 500,
"attempt": 3,
"duration_ms": 5012,
"delivered_at": "2026-05-26T10:14:20Z"
}
]
} Signature Verification
Every delivery includes an X-Miosa-Signature header. Verify it on your server before trusting the payload:
X-Miosa-Signature: sha256=<hex_digest> Compute the expected signature:
hmac = HMAC-SHA256(secret, raw_request_body_bytes)
expected = "sha256=" + hex(hmac)
assert constant_time_equals(expected, request.headers["X-Miosa-Signature"]) Example verification (Python)
import hmac, hashlib
def verify_signature(secret: str, body: bytes, header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header) Example verification (TypeScript)
import { createHmac, timingSafeEqual } from 'crypto';
function verifySignature(secret: string, body: Buffer, header: string): boolean {
const expected = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
return timingSafeEqual(Buffer.from(expected), Buffer.from(header));
} Retry Policy
Failed deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4+ | Up to retry_count × 10 minutes (capped at 6 hours) |
A delivery is considered failed if your endpoint returns a non-2xx status code or does not respond within 30 seconds. Set retry_count: 0 to disable retries.
Subscribed Event Types
| Event | Description |
|---|---|
sandbox.created | Sandbox created |
sandbox.ready | Sandbox reached running |
sandbox.error | Sandbox failed to start |
sandbox.destroyed | Sandbox deleted |
computer.created | Computer created |
computer.running | Computer reached running |
computer.stopped | Computer stopped |
computer.deleted | Computer deleted |
deployment.succeeded | Build succeeded and promoted |
deployment.failed | Build or promotion failed |
deployment.rollback | Rollback triggered |
billing.credit_low | Credit balance below threshold |
billing.credit_exhausted | Credit balance reached zero |
webhook.test | Synthetic test delivery |
Subscribe to all events by omitting the events field. Subscribe to a subset by listing specific event types.