Webhooks and events
Everything that happens in Slipway is an event on an internal stream. Events feed notifications, the audit log, metering and outbound webhooks.
Event envelope
Section titled “Event envelope”{ "id": "01H...", "org_id": "org_01H...", "type": "deployment.succeeded", "at": "2026-10-21T10:15:00Z", "actor": { "type": "user", "id": "user_01H...", "name": "Ada" }, "data": { "deployment_id": "dep_01H...", "environment": "production", "release": "1.4.0" }}Event types follow <domain>.<action>, for example run.started, run.finished, job.queued, deployment.waiting_approval, deployment.succeeded, deployment.failed, deployment.rolled_back, release.created, runner.offline.
Outbound webhooks
Section titled “Outbound webhooks”Create a webhook with a URL and the events you want (WebhookService.CreateWebhook, or Settings, Webhooks). The signing secret is shown once.
Each delivery is an HTTPS POST with JSON and these headers:
| Header | Value |
|---|---|
X-Slipway-Event |
The event type |
X-Slipway-Delivery |
A unique delivery id (use it to de-duplicate) |
X-Slipway-Signature |
t=<unix seconds>,v1=<hex HMAC-SHA256> |
Verifying the signature
Section titled “Verifying the signature”The signature is the HMAC-SHA256, keyed with your webhook secret, of the string "<t>.<raw body>". Recompute it, compare in constant time and reject old timestamps.
func verify(secret, header string, body []byte) bool { var t, v1 string for _, p := range strings.Split(header, ",") { k, v, _ := strings.Cut(p, "=") switch k { case "t": t = v; case "v1": v1 = v } } ts, err := strconv.ParseInt(t, 10, 64) if err != nil || time.Since(time.Unix(ts, 0)).Abs() > 5*time.Minute { return false } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(t + ".")) mac.Write(body) want, _ := hex.DecodeString(v1) return hmac.Equal(mac.Sum(nil), want)}import crypto from "node:crypto";export function verify(secret, header, rawBody) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const mac = crypto.createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest(); const want = Buffer.from(parts.v1, "hex"); return want.length === mac.length && crypto.timingSafeEqual(mac, want) && Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;}Delivery, retries and redelivery
Section titled “Delivery, retries and redelivery”- Respond with any
2xxwithin a few seconds to acknowledge. - Failures are retried with backoff. Every attempt (status, response, timing) is kept in the delivery log (
ListWebhookDeliveries). - Redeliver any delivery manually (
RedeliverWebhook) and send a test event (PingWebhook). - Rotate the secret with
RotateWebhookSecret. - Handlers should be idempotent: the same delivery id can arrive more than once.
Inbound webhooks
Section titled “Inbound webhooks”| Path | Sender | Verification |
|---|---|---|
POST /hooks/scm/{provider}/{connection_id} |
GitHub, GitLab, Bitbucket, Azure Repos, Gitea | Provider signature or token, checked before parsing |
POST /hooks/integrations/{provider}/{integration_id} |
Third-party integrations | Provider-specific |
POST /hooks/generic/{pipeline_id} |
Anything, to trigger a pipeline with the webhook trigger |
HMAC with the trigger secret |
All inbound webhooks are verified before the body is parsed, and bodies are size-limited.
Subscriptions to integrations
Section titled “Subscriptions to integrations”Notification and incident integrations use subscriptions: an event filter plus a target (channel, routing key, project key). See Integrations.
Personal notifications
Section titled “Personal notifications”Besides organisation-wide subscriptions, each member has an inbox (the bell) and per-organisation preferences under Settings → Notifications. The notifications worker turns events into entries for the people they concern, in the app and by email:
| Preference | Who receives it |
|---|---|
| Run failed | The user who started the run |
| Run fixed | The user who started a run that succeeds after the pipeline’s previous run failed |
| Approval requested | Members allowed to approve deployments |
| Deployment finished | The user who started the deployment (succeeded, failed, rolled back or cancelled) |
| Runner offline | Members who manage runners |
| Usage threshold | Members who manage billing, plus the extra recipients in the billing alert settings |
NotificationService exposes the same inbox and preferences to API clients (ListNotifications, MarkNotificationsRead, WatchNotifications, GetNotificationPreferences, UpdateNotificationPreferences). Entries are kept for 90 days.