number.released
Fires when a phone number is released from the workspace and returned to inventory.
number.released fires when a DID is unassigned from the workspace and returned to inventory. This can be triggered by an explicit release request, trial expiry termination, or an admin action. Use this event to clean up routing configuration, remove the number from internal phone books, and stop billing it in your own systems.
This event is subscribable via workspace webhooks (POST /v1/webhooks).
{
"kind": "number.released",
"event_id": "01900000-0000-7000-8000-000000000001",
"workspace_id": "01900000-0000-7000-8000-000000000002",
"occurred_at": "2026-06-27T10:00:00.000Z",
"data": {
"number_id": "01900000-0000-7000-8000-000000000003",
"e164": "+254700000001",
"country_code": "KE",
"release_reason": "explicit",
"released_at": "2026-06-27T10:00:00.000Z"
}
}Possible release_reason values: explicit (caller-initiated), trial_expired, admin_released.
Every webhook delivery includes the following request headers:
| Header | Description |
|---|---|
X-Sautikit-Signature | HMAC-SHA256 of the raw body, hex-encoded. Verify with your subscription secret. |
X-Sautikit-Idempotency-Key | Unique delivery ID for deduplication. |
X-Sautikit-Event | Literal event kind: number.released. |
event_id or the X-Sautikit-Idempotency-Key header.dead_letter.import { createHmac } from "node:crypto";
export async function POST(req) {
const sig = req.headers["x-sautikit-signature"];
const body = await req.text();
const expected = createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(body)
.digest("hex");
if (sig !== expected) return new Response("Forbidden", { status: 403 });
const event = JSON.parse(body);
if (event.kind === "number.released") {
const { number_id, e164, release_reason } = event.data;
console.log(`Number ${e164} released (${release_reason}): ${number_id}`);
// remove from routing config, update CRM...
}
return new Response("OK", { status: 200 });
}events: ["number.released"].number.provisioned to track full number lifecycle.