Publieke testfase We zitten in een publieke testfase — kijk gerust rond, maar bestellen kan nog niet. Bestellen kan vanaf 1 oktober 2026. Bekijk prijzen →

Webhooks, Slack en Teams

veldhost can push what happens to your sites to your own systems — a CI pipeline, an incident tool, a dashboard — or straight into a Slack or Microsoft Teams channel. Every event goes through the same pipeline: signed, retried, and logged where you can see it. Set it up under Account → Integrations.

Events

Pick any of these when you add an endpoint. Events cover every site on your account, not just your own.

deploy.succeeded         a deploy finished and the new version is serving
deploy.failed            a deploy failed; the previous version keeps serving
backup.completed         a backup finished (data.ok says whether it succeeded)
restore.completed        a restore you asked for finished (data.ok)
site.down                a site has been failing its health probe for 15 minutes
site.up                  it is answering again (data.downtime_minutes)
incident.opened          we opened a customer-visible incident touching your site
incident.resolved        that incident was resolved
domain.expiring          a registered domain is 30 or 7 days from expiry, or has expired
invoice.paid             an invoice was paid
invoice.payment_failed   a payment failed — update your card to keep your sites online
webhook.test             sent by the "Send test event" button; never subscribable

What a delivery looks like

A generic webhook receives one HTTP POST per event with a JSON body:

POST /your/receiver HTTP/1.1
Content-Type: application/json
X-Veldhost-Event: deploy.succeeded
X-Veldhost-Delivery: 7c1a0e2e-5b0b-4f0f-9a5e-2c6b6a1d3f10
X-Veldhost-Signature: sha256=4f0c…e2a1

{
  "id": "7c1a0e2e-5b0b-4f0f-9a5e-2c6b6a1d3f10",
  "event": "deploy.succeeded",
  "created_at": "2026-09-10T09:14:02+00:00",
  "data": {
    "site": { "slug": "my-site", "name": "My site", "url": "https://example.com" },
    "ref": "main"
  }
}

id is unique per delivery and repeats on every retry of the same delivery, so use it to de-duplicate. data differs per event: site events carry site; incidents carry incident (id, title, severity, status, started_at, resolved_at) and sites; domain.expiring carries domain, days_left, expires_at, auto_renew; invoice events carry invoice (number, amount, currency, url).

Answer with any 2xx status within 10 seconds. Do the real work afterwards — a receiver that does its processing before answering is the usual reason for timeouts and duplicate deliveries.

Verifying the signature

When you add a webhook you are shown a secret once (whsec_…). Every delivery carries X-Veldhost-Signature: sha256=<hex>, the HMAC-SHA256 of the raw request body under that secret. Compute it yourself from the raw bytes — before any JSON parsing or re-encoding — and compare with a constant-time comparison. Reject anything that does not match.

PHP

$secret = getenv('VELDHOST_WEBHOOK_SECRET');
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);

if (! hash_equals($expected, $_SERVER['HTTP_X_VELDHOST_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit;
}

$event = json_decode($raw, true);
// $event['event'], $event['data'] ...
http_response_code(204);

Node.js (Express — keep the raw body; a JSON body parser must not run first):

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const secret = process.env.VELDHOST_WEBHOOK_SECRET;

app.post('/hooks/veldhost', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(req.body).digest('hex');
  const given = req.get('X-Veldhost-Signature') ?? '';

  if (expected.length !== given.length || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given))) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // event.event, event.data ...
  res.sendStatus(204);
});

Lost the secret? Delete the endpoint and add it again — a new secret is generated and shown once.

Retries and pausing

A delivery that gets no 2xx — a timeout, a connection error, a 4xx or 5xx — is retried after one minute and again after ten, three attempts in all. Each attempt sends the same body and the same X-Veldhost-Delivery id. The delivery log on the Integrations page shows the response code and the first part of the response body for every delivery of the last 200.

After twenty deliveries in a row fail their final attempt, the endpoint is paused and you get an email. Fix the receiver, then press Enable; nothing from the paused period is replayed. A single successful delivery resets the failure count.

Slack

  1. In Slack, open the channel's settings → IntegrationsAdd an appIncoming WebHooks (or create a Slack app with an incoming webhook), and copy the URL that starts with https://hooks.slack.com/.
  2. Under Integrations in veldhost Manage, choose kind Slack, paste the URL and pick your events.
  3. Press Send test event. A message should appear in the channel within a few seconds.

Each event arrives as a short message with the site, what happened, and a few facts (a URL, how long an outage lasted, an invoice number). The signature headers are still sent; Slack ignores them.

Microsoft Teams

  1. In Teams, open the channel → WorkflowsPost to a channel when a webhook request is received (or a classic Incoming Webhook connector where still available) and copy the URL.
  2. Choose kind Microsoft Teams, paste the URL and pick your events.
  3. Press Send test event.

Teams receives an Adaptive Card (a message with one application/vnd.microsoft.card.adaptive attachment), which both Workflows and the classic connector accept.

Over the API

The same endpoints are on the customer API with the manage scope:

GET    /api/v1/webhooks                 list your endpoints and the subscribable events
POST   /api/v1/webhooks                 {"kind": "generic", "url": "https://…", "events": ["deploy.failed", "site.down"]}
                                        → 201 with the secret, once
DELETE /api/v1/webhooks/{id}            remove an endpoint
POST   /api/v1/webhooks/{id}/test       queue a webhook.test delivery

Everything you do here is recorded in your account's audit trail, like every other API write.