Webhooks
Receive a signed HTTPS request when a microVM of your application is created, starts, pauses, stops or fails.
A webhook sends a signed POST request to your HTTPS endpoint whenever a microVM of your application changes state. Use one to react to a microVM without polling it, for example to clean up after one stops or to alert when one fails.
Events
| Event | Sent when |
|---|---|
microvm.created | A microVM is created |
microvm.running | A microVM starts, or resumes after a pause |
microvm.paused | A microVM finishes pausing |
microvm.stopped | A microVM stops, for example when you stop it or its timeout ends |
microvm.failed | A microVM could not start, or stopped running unexpectedly |
Each webhook receives only the events it subscribes to, for every microVM of its application.
Add a webhook
In the console, open your application, go to microVMs, then Webhooks, and select Add webhook. Enter the endpoint URL, choose the events, and copy the signing secret that the console shows next.
To add one in code or from a terminal, pass the URL and the events:
import { GigadriveClient } from '@gigadrive/sdk';
const client = new GigadriveClient({ applicationId: '0197b2f1-2f4a-7a0b-8a2d-222222222222' });
const webhook = await client.microvms.webhooks.create({
url: 'https://example.com/hooks/microvms',
events: ['microvm.running', 'microvm.stopped', 'microvm.failed'],
description: 'deploy bot',
});
console.log(webhook.id, webhook.secret);client.microvms.webhooks also has list, get, update, delete, rotateSecret and listDeliveries.
The result is the webhook with its secret, a string that starts with whsec_. Store the secret now: it is never shown again. If you lose it, replace it with POST /microvm-webhooks/{id}/secret, which answers with the new one. The old secret stops signing requests at once.
The URL must use https and name a public host. Private and local addresses, host names that end in .local or .internal, and URLs with a user name or password are refused with 400. An application can have 10 webhooks; an eleventh answers 429.
Manage webhooks under /microvm-webhooks:
| Call | Does |
|---|---|
GET /microvm-webhooks?applicationId=… | Lists the application's webhooks, without their secrets |
GET /microvm-webhooks/{id} | Reads one webhook |
PATCH /microvm-webhooks/{id} | Changes url, events, description or enabled |
POST /microvm-webhooks/{id}/secret | Replaces the signing secret |
GET /microvm-webhooks/{id}/deliveries | Lists the 50 most recent deliveries |
DELETE /microvm-webhooks/{id} | Deletes the webhook and the deliveries it still had to send |
Set "enabled": false to pause a webhook: it receives no new events, and deliveries already waiting for a retry are held until you enable it again. Reading webhooks needs the network:microvms:read scope, and adding, changing or deleting them needs network:microvms:write.
The request
Each event arrives as a POST with a JSON body:
{
"type": "microvm.stopped",
"timestamp": "2026-11-01T12:00:05.000Z",
"data": {
"microvmId": "0199a7c4-5b1e-7c3d-9f20-333333333333",
"applicationId": "0197b2f1-2f4a-7a0b-8a2d-222222222222",
"organizationId": "0197b2f1-2f4a-7a0b-8a2d-111111111111",
"status": "stopped",
"previousStatus": "running"
}
}timestamp is when the microVM changed state. previousStatus is the state it left, and null for microvm.created. Read the microVM with GET /microvms/{id} when you need more than its state, such as its stopReason.
The request carries three headers that follow the Standard Webhooks specification:
| Header | Value |
|---|---|
webhook-id | The event's ID, the same on every retry |
webhook-timestamp | When this attempt was sent, in seconds since the Unix epoch |
webhook-signature | v1, followed by the base64 HMAC-SHA256 signature of the request |
Answer with any 2xx status within 10 seconds to confirm the delivery. Redirects are not followed, so a 3xx counts as a failure.
Verify the signature
Check every request before you trust it. In TypeScript, verifyMicroVmWebhook checks the signature and the timestamp and returns the event. This route handler works in any runtime with the web Request type:
import { MicroVmWebhookVerificationError, verifyMicroVmWebhook } from '@gigadrive/sdk';
export async function POST(request: Request): Promise<Response> {
try {
const event = await verifyMicroVmWebhook({
body: await request.text(),
headers: request.headers,
secret: process.env.MICROVM_WEBHOOK_SECRET!,
});
console.log(event.type, event.data.microvmId);
return new Response(null, { status: 204 });
} catch (error) {
if (error instanceof MicroVmWebhookVerificationError) return new Response(null, { status: 401 });
throw error;
}
}In other languages, any Standard Webhooks library verifies the request. The signature is an HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, keyed with the base64-decoded part of your secret after whsec_. This Node.js server checks it with the built-in crypto module:
import { createHmac, timingSafeEqual } from 'node:crypto';
import { createServer } from 'node:http';
const key = Buffer.from(process.env.WEBHOOK_SECRET!.replace(/^whsec_/, ''), 'base64');
createServer((req, res) => {
const chunks: Buffer[] = [];
req.on('data', (chunk: Buffer) => chunks.push(chunk));
req.on('end', () => {
const body = Buffer.concat(chunks).toString('utf8');
const id = String(req.headers['webhook-id']);
const timestamp = String(req.headers['webhook-timestamp']);
const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${body}`).digest();
const signatures = String(req.headers['webhook-signature'] ?? '').split(' ');
const valid = signatures.some((entry) => {
const received = Buffer.from(entry.replace(/^v1,/, ''), 'base64');
return received.length === expected.length && timingSafeEqual(received, expected);
});
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
if (!valid || !fresh) {
res.writeHead(401).end();
return;
}
const event = JSON.parse(body);
console.log(event.type, event.data.microvmId);
res.writeHead(204).end();
});
}).listen(3000);Verify against the raw body exactly as it arrived; parsing and re-serializing the JSON changes the bytes. Rejecting timestamps more than five minutes away from your clock stops an old request from being replayed; verifyMicroVmWebhook does this by default, and toleranceSeconds changes the window.
Retries and ordering
Events are usually sent within a few seconds. A delivery that times out, cannot connect, or gets a status other than 2xx is retried 6 times, after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours. After the seventh failed attempt it is marked failed and not sent again.
Events can arrive out of order, and the same event can arrive more than once. Order them by timestamp, and use webhook-id to skip an event you already handled.
To see what was sent, select Deliveries next to a webhook in the console, or call GET /microvm-webhooks/{id}/deliveries. Each delivery shows its event, status (pending, delivered or failed), the number of attempts, the last HTTP status and error, and when the next attempt is due. Deliveries are kept for 7 days.
Related
- Metrics show a microVM's CPU, memory and disk use.
- Pause, resume and snapshots explains when a microVM pauses and resumes.
