Integrate your own hosting solution into Laioutr by setting up a webhook. Cockpit will call this webhook for every deployment-related action:
You provide a URL that Cockpit will call for each of these actions.
All requests from Cockpit are signed using the Standard Webhooks specification. When you configure your webhook, you'll receive a signing secret (prefixed with whsec_) that you must use to verify incoming requests.
Each request includes these headers:
| Header | Description |
|---|---|
webhook-id | Unique identifier for this webhook delivery |
webhook-timestamp | Unix timestamp (seconds) when the request was sent |
webhook-signature | HMAC-SHA256 signature in format v1,{base64} |
To verify a request:
{webhook-id}.{webhook-timestamp}.{body} (body is the raw request body)whsec_ prefix from your signing secretv1, prefix (timing-safe comparison)Most languages have Standard Webhooks libraries available. See standardwebhooks.com for implementations.
All requests are POST with Content-Type: application/json. Every request includes:
| Field | Type | Description |
|---|---|---|
event | string | The event type (e.g., hosting.deployment.created) |
timestamp | string | ISO 8601 timestamp (UTC, e.g., 2025-01-30T12:00:00.000Z) of when the http-request was sent |
project | string | Project identifier as org-slug/project-slug |
data | object | Event-specific payload (optional) |
{
"event": "hosting.deployment.created",
"timestamp": "2025-01-30T12:00:00.000Z",
"project": "acme-corp/storefront",
"data": { ... }
}
TypeScript definitions for all webhook events and responses are available in the @laioutr/webhook-types package:
npm install @laioutr/webhook-types
Usage example:
import type { ByosWebhookEvent, ByosDescribeResponse, ByosWebhookResponse } from '@laioutr/webhook-types/byos';
function handleWebhook(event: ByosWebhookEvent): ByosWebhookResponse {
if (event.event === 'hosting.describe') {
return {
ok: true,
data: {
name: 'My CI/CD System',
url: 'https://storefront.example.com',
capabilities: {
/* ... */
},
},
} satisfies ByosDescribeResponse;
}
if (event.event === 'hosting.deployment.created') {
const { deploymentId, callbackUrl, files } = event.data;
startBuild(deploymentId, files, callbackUrl);
}
return { ok: true, data: {} };
}
Cockpit retries failed webhook deliveries with the following policy:
| Parameter | Value |
|---|---|
| Max attempts | 3 |
| Per-attempt timeout | 7 seconds |
| Total time budget | 25 seconds |
| Retry delay | Exponential backoff (1s, 2s, 4s) with 30% jitter |
A delivery is considered failed if:
Returning { "ok": false, "error": "..." } will not trigger a retry.
If the retries did not succeed, the event will be discarded.
The webhook-id header serves as an idempotency key. The same webhook-id is used across all retry attempts for a given event. Your endpoint should use this to deduplicate requests if needed.
First attempt: webhook-id: evt_abc123
Retry 1: webhook-id: evt_abc123 (same)
Retry 2: webhook-id: evt_abc123 (same)
You can track webhook-id values to skip duplicates.
Your endpoint must respond with JSON in this format:
{
"ok": true,
"data": { ... }
}
Or on failure:
{
"ok": false,
"error": "Human-readable error message"
}
Return appropriate HTTP status codes:
200 for successful processing400 for invalid requests401 for authentication failures500 for server errorshosting.describeCockpit sends this event to discover your system's capabilities. This is called during initial setup and periodically to refresh capabilities.
{
"event": "hosting.describe",
"timestamp": "2025-01-30T12:00:00.000Z",
"project": "acme-corp/storefront"
}
{
"ok": true,
"data": {
"name": "Your CI/CD System",
"url": "https://storefront.example.com",
"capabilities": {
"statusUpdates": true,
"cancelDeployment": false,
"promoteDeployment": false,
"rollbackDeployment": false,
"deleteDeployment": false
}
}
}
| Field | Description |
|---|---|
name | Display name for your hosting provider (shown in Cockpit UI) |
url | Base URL where the project is hosted (e.g., https://storefront.example.com). Will be used in the studio preview |
capabilities | Object describing which actions your system supports |
| Capability | Description |
|---|---|
statusUpdates | Your system will call back with deployment status updates. If this capability is not supported, the Cockpit deployment status will be set to unknown. |
cancelDeployment | Your system can cancel in-progress deployments |
promoteDeployment | Your system can promote deployments to production |
rollbackDeployment | Your system can rollback to previous deployments |
deleteDeployment | Your system can delete deployments |
Set capabilities to true only for actions your system supports. Cockpit will only send those event types if you indicate support.
hosting.connectedSent when a project successfully connects to your webhook. Use this to set up any resources you need for the project.
{
"event": "hosting.connected",
"timestamp": "2025-01-30T12:00:00.000Z",
"project": "acme-corp/storefront"
}
{
"ok": true,
"data": {}
}
hosting.disconnectedSent when a project disconnects from your webhook. Use this to clean up any resources.
{
"event": "hosting.disconnected",
"timestamp": "2025-01-30T12:00:00.000Z",
"project": "acme-corp/storefront"
}
{
"ok": true,
"data": {}
}
hosting.deployment.createdSent when a user triggers a deployment. Contains all files needed to build and deploy the project.
{
"event": "hosting.deployment.created",
"timestamp": "2025-01-30T12:00:00.000Z",
"project": "acme-corp/storefront",
"data": {
"deploymentId": "dep_abc123",
"environment": "production",
"callbackUrl": "https://cockpit.laioutr.cloud/api/webhook/hosting/dep_abc123?secret=cbsec_xxx",
"files": {
"package.json": "{ \"name\": \"storefront\", ... }",
"nuxt.config.ts": "export default defineNuxtConfig({ ... })",
"laioutrrc.json": "{ ... }",
"app.vue": "<template>...</template>"
}
}
}
| Field | Description |
|---|---|
deploymentId | Unique identifier for this deployment |
environment | Either "production" or "staging" |
callbackUrl | URL to POST status updates (see Status Callbacks) |
files | Map of filename to file contents |
Acknowledge receipt immediately. Don't wait for the build to complete.
{
"ok": true,
"data": {}
}
hosting.deployment.cancelSent when a user requests to cancel an in-progress deployment. Only sent if you indicated cancelDeployment: true in capabilities.
{
"event": "hosting.deployment.cancel",
"timestamp": "2025-01-30T12:00:00.000Z",
"project": "acme-corp/storefront",
"data": {
"deploymentId": "dep_abc123"
}
}
{
"ok": true,
"data": {}
}
hosting.deployment.promoteSent when a user wants to promote a staging deployment to production. Only sent if you indicated promoteDeployment: true in capabilities.
{
"event": "hosting.deployment.promote",
"timestamp": "2025-01-30T12:00:00.000Z",
"project": "acme-corp/storefront",
"data": {
"deploymentId": "dep_abc123"
}
}
{
"ok": true,
"data": {}
}
hosting.deployment.rollbackSent when a user wants to rollback to a previous deployment. Only sent if you indicated rollbackDeployment: true in capabilities.
{
"event": "hosting.deployment.rollback",
"timestamp": "2025-01-30T12:00:00.000Z",
"project": "acme-corp/storefront",
"data": {
"deploymentId": "dep_abc123",
"fromDeploymentId": "dep_xyz789"
}
}
| Field | Required | Description |
|---|---|---|
deploymentId | Yes | Deployment to roll back TO |
fromDeploymentId | No | Currently active deployment being replaced |
{
"ok": true,
"data": {}
}
hosting.deployment.deleteSent when a user wants to delete a deployment. Only sent if you indicated deleteDeployment: true in capabilities.
{
"event": "hosting.deployment.delete",
"timestamp": "2025-01-30T12:00:00.000Z",
"project": "acme-corp/storefront",
"data": {
"deploymentId": "dep_abc123"
}
}
{
"ok": true,
"data": {}
}
If you set statusUpdates: true in your capabilities, you should POST status updates to the callbackUrl provided in the deployment request.
The callback URL is provided in the hosting.deployment.created event:
https://cockpit.laioutr.cloud/api/webhook/hosting/{deploymentId}?secret={secret}
The deploymentId is embedded in the URL path. The secret parameter (prefixed with cbsec_) authenticates your request. No additional headers or signatures are required.
The following diagram shows the valid deployment status transitions:
State Transition Rules:
canceled is a terminal state - no transitions are allowed after cancellationerror back to runningSend a POST request with Content-Type: application/json.
Note: Status callbacks do not include the project field. The deployment is identified by the deploymentId in the callback URL path.
Indicate that the deployment is in progress:
{
"event": "hosting.deployment.status",
"timestamp": "2025-01-30T12:05:00.000Z",
"data": {
"status": "running"
}
}
Indicate that deployment succeeded. The url field is required and must be a valid URL:
{
"event": "hosting.deployment.status",
"timestamp": "2025-01-30T12:10:00.000Z",
"data": {
"status": "success",
"url": "https://storefront.example.com"
}
}
Indicate that the deployment failed. The error field is required:
{
"event": "hosting.deployment.status",
"timestamp": "2025-01-30T12:10:00.000Z",
"data": {
"status": "error",
"error": "Build failed: npm install returned exit code 1"
}
}
Indicate that the deployment was canceled:
{
"event": "hosting.deployment.status",
"timestamp": "2025-01-30T12:08:00.000Z",
"data": {
"status": "canceled"
}
}
Indicate that a deployment was promoted to production. The url field is optional:
{
"event": "hosting.deployment.status",
"timestamp": "2025-01-30T12:15:00.000Z",
"data": {
"status": "promoted",
"url": "https://storefront.example.com"
}
}
Cockpit responds with:
{
"ok": true,
"data": {}
}
Or on error:
{
"ok": false,
"error": "Deployment not found"
}
| Status | Meaning |
|---|---|
| 200 | Status update accepted |
| 400 | Invalid payload format |
| 401 | Missing or invalid callback secret |
| 404 | Deployment not found |
| 500 | Server error |
Note: Invalid status transitions (e.g., updating a canceled deployment) return 200 with { "ok": true } but are silently ignored.
If Cockpit is temporarily unavailable when sending status callbacks:
whsec_) and configure it in your systemYour webhook will now receive events for all deployment actions.
whsec_ prefix from the secretv1,{base64} - extract the base64 part after v1, for comparison200 status codescbsec_ prefix) is included in the query stringurl field is required for success status - invalid URLs are rejected with 400