Webhooks
A webhook is a signed POST that Alien sends to your HTTPS endpoint when a deployment starts running, fails, waits for its owner, or stops reporting. You hear about the change at once, without polling:
{
"id": "evt_6f1c2b9e-3a4d-4e8f-9b7a-2c5d8e1f0a3b",
"type": "deployment.failed",
"schemaVersion": 1,
"occurredAt": "2026-10-25T14:03:11.482Z",
"workspaceId": "ws_Q2x8LmV4nR7tY1kP9sD3fG6h",
"projectId": "prj_4k8m2q9x7v1c5n3b6z0t8r2w4y6p",
"data": {
"deploymentId": "dep_7h3k9m2x5q8v1c4n6b0z2t7r9w3y",
"deploymentGroupId": "dg_2m6q9x4v7c1n5b8z3t0r6w2y4p8k",
"name": "us-east-1",
"status": "provisioning-failed",
"releaseId": null,
"error": {
"message": "Deployment needs attention. Read its current status for details.",
"resourceId": "api"
}
}
}Use it to update your product, page your team, or start a workflow. A deployment is one running copy of your code in one remote environment. Its owner is the customer's admin who ran initial setup, the one-time step in their account.
Polling GET /v1/deployments/{id} still fits a page a customer is watching. Webhooks fit everything else: hear about a change, then read that one deployment. See Show deployment progress.
Create an endpoint
In the dashboard, open Settings → Webhooks and select Add webhook:
- Under Projects, choose All projects or Specific projects.
- Under Events, every event starts selected. Clear the ones your receiver doesn't handle.
- Enter your HTTPS URL and an optional description, then select Create webhook.
- Open the endpoint and select Reveal under Signing secret. Store the
whsec_…value on your server asALIEN_WEBHOOK_SECRET.
Managing webhooks needs a workspace admin. Through the API, a project admin key (alien api-keys create --for project-admin) manages endpoints that cover only its own project:
- It names the project in
projectIdswhen it creates an endpoint. - It passes the
projectIdquery parameter on every later call. - A project's ID (
prj_…) is theidfromGET /v1/projects/docproc.
import { alien } from "./alien"
const projectId = "prj_4k8m2q9x7v1c5n3b6z0t8r2w4y6p"
export async function createEndpoint() {
const endpoint = await alien.webhooks.createWebhookEndpoint({
url: "https://acme.dev/webhooks/alien",
description: "Deployment updates",
filterTypes: ["deployment.running", "deployment.failed"],
projectIds: [projectId],
})
const { key } = await alien.webhooks.getWebhookSigningSecret({ id: endpoint.id, projectId })
return { id: endpoint.id, secret: key }
}- Leave out
filterTypesto get every event, including ones added later. - Leave out
projectIdsto cover every project in the workspace. That needs a workspace admin. - One endpoint can list at most 10 projects. Create another endpoint for more.
- An endpoint's projects can't change after you create it.
- The URL must use HTTPS.
Alien starts recording events when your workspace creates its first endpoint. Earlier history isn't sent.
| Method and path | Use |
|---|---|
GET, POST /v1/webhooks/endpoints | List or create endpoints |
GET, PATCH, DELETE /v1/webhooks/endpoints/{id} | Read, update, or delete one. PATCH takes url, description, filterTypes, and disabled, and changes only the fields you send. |
GET /v1/webhooks/endpoints/{id}/secret | Read the signing secret |
POST /v1/webhooks/endpoints/{id}/rotate-secret | Rotate the signing secret |
GET /v1/webhooks/endpoints/{id}/deliveries | Recent delivery attempts |
POST /v1/webhooks/endpoints/{id}/retry | Send a message again ({ "messageId": "…" }) |
POST /v1/webhooks/endpoints/{id}/test | Send a test event |
Events
Every deployment event carries data.deploymentId, data.deploymentGroupId, data.name, data.status, and data.releaseId.
| Event | When it fires | Also in data |
|---|---|---|
deployment.created | A deployment is created. | |
deployment.running | The status changes to running, including after an update or a recovered failure. | |
deployment.failed | The status changes to error or any *-failed status except refresh-failed. | error.message, error.resourceId (the first failed resource, or null) |
deployment.update_blocked | An update waits for the deployment owner to rerun initial setup or provide an input. | operationId, operationStatus (blocked) |
deployment.update_applied | An update finishes. | operationId, operationStatus (succeeded) |
deployment.teardown_required | Removal waits for the deployment owner to delete what initial setup created. | |
deployment.deleted | The deployment is deleted. | |
deployment.heartbeat_lost | A running deployment hasn't sent a heartbeat for 5 minutes. | |
deployment.heartbeat_restored | A deployment that lost its heartbeat reports again. | |
setup_link.used | A deployment group's setup link is used for the first time. Initial setup may still be in progress. | setupLinkId, deploymentGroupId only |
On update events, releaseId is the release being applied. Heartbeat events skip deployments with heartbeats turned off. See Ready and healthy.
occurredAt is when the change happened, not when it was sent. Alien may add fields within schemaVersion 1. Ignore fields and event types you don't recognize.
error.message is a fixed summary. It never contains provider messages, logs, environment values, or resource outputs. Read the deployment for the full error.
Verify signatures
Each request carries the Standard Webhooks headers webhook-id, webhook-timestamp, and webhook-signature. Verify them against the raw request body before you trust it. A body parsed and serialized again won't match.
npm install standardwebhooksimport { Webhook } from "standardwebhooks"
import { saveEvent } from "@/lib/inbox"
const webhook = new Webhook(process.env.ALIEN_WEBHOOK_SECRET!)
export async function POST(request: Request) {
const body = await request.text()
let event: unknown
try {
event = webhook.verify(body, Object.fromEntries(request.headers))
} catch {
return new Response("Invalid signature", { status: 400 })
}
// Store it and return. A worker processes it later.
await saveEvent(request.headers.get("webhook-id")!, event)
return new Response(null, { status: 204 })
}The libraries also reject a webhook-timestamp more than 5 minutes from your server's clock, so keep the clock in sync.
Respond quickly
Return a 2xx status as soon as the event is stored. Any other status, a redirect, or no response within 30 seconds counts as a failure and is retried. Do the work, such as reading the deployment or emailing the customer, in a background job.
Retries
Alien retries a failed delivery on this schedule after the first attempt: 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and 10 hours.
Recent deliveries on the endpoint page lists each attempt with its response status. Select Retry to send a message again after you fix your receiver, or call POST /v1/webhooks/endpoints/{id}/retry with its messageId.
If every delivery to an endpoint fails for 5 days, Alien disables the endpoint. Fix the receiver, then select Enable endpoint in Settings → Webhooks. Disable endpoint stops deliveries on purpose, and Delete endpoint removes the endpoint.
Duplicates
The same event can arrive more than once. webhook-id is the same on every delivery of one event, including retries, so store it with a unique constraint and skip events you've seen:
INSERT INTO alien_events (webhook_id, payload, received_at)
VALUES ($1, $2, now())
ON CONFLICT (webhook_id) DO NOTHING;Return 2xx for a duplicate too. Don't deduplicate on deploymentId: a deployment can fail, recover, and fail again, and each change is a new event.
Ordering
Events can arrive out of order, for example when a retry lands after a newer event. Compare occurredAt with the last event you applied for that deployment, and ignore older ones. Before acting on a status, especially before deleting anything, read the current deployment with GET /v1/deployments/{id}.
Rotate the secret
Select Rotate under Signing secret, or call POST /v1/webhooks/endpoints/{id}/rotate-secret. Then read the new secret with Reveal or GET /v1/webhooks/endpoints/{id}/secret, and deploy it to your receiver.
The old secret stays valid for 24 hours. During that time each request is signed with both secrets, so a receiver with either one keeps verifying.
Test events
Select Send test event on the endpoint page, or call POST /v1/webhooks/endpoints/{id}/test. Alien sends a webhook.test event to that endpoint only. You don't need to subscribe to it.
{
"id": "evt_example",
"type": "webhook.test",
"schemaVersion": 1,
"occurredAt": "2026-01-01T00:00:00.000Z",
"workspaceId": "ws_example",
"projectId": null,
"data": { "message": "This is a test webhook from Alien." }
}The IDs in a test event are placeholders. Verify the signature, return 2xx, and don't look anything up. Every test event has the same id, so deduplicate tests by webhook-id.
Local testing
Expose your local receiver through an HTTPS tunnel, such as cloudflared or ngrok:
cloudflared tunnel --url http://localhost:3000Create an endpoint with the tunnel's URL plus your route, such as https://<tunnel-host>/webhooks/alien. Send a test event and check Recent deliveries for a 2xx. Then deploy to a test environment to receive real events with real IDs.
To test retries, return 500 once and watch the retry arrive. To test verification, change one byte of a saved body and check that your receiver rejects it.
Next
- Deployments and groups: statuses and heartbeats behind each event.
- Show deployment progress: read the deployment when an event arrives.
- Troubleshooting: what to do about a failed deployment.
Follow a deployment
Watch a customer's initial setup and the deployment after it from the dashboard or the CLI, and know when to step in and when the admin must act.
Logs and metrics
Search the logs every deployment sends, read health and resource metrics, and forward logs to Datadog, Coralogix, Axiom, or any OTLP endpoint.