NewHost Docs

Webhooks

Webhooks tell your systems when something happens in your NewHost account, such as a deployment finishing, a payment clearing or a domain being registered. NewHost sends an HTTPS POST with a JSON body to each endpoint subscribed to that event.

Adding an endpoint

  • In the dashboard: Developers → Webhooks → Add endpoint, or via the API with POST /v1/webhooks.
  • The URL must be public and use https://.
  • Pick the events you want, or all of them. You get a signing secret (whsec_…) to verify requests.
  • Use Send test event to deliver a ping event and see your endpoint's response.

Event types

EventWhen
app.createdAn application was created
app.deletedAn application was deleted
deployment.startedA deployment began running
deployment.succeededA deployment finished successfully
deployment.failedA deployment failed
domain.registeredA domain was registered
database.createdA database was provisioned
payment.succeededA payment was accepted by Netcash
payment.failedA payment was declined
subscription.activatedA plan subscription became active or renewed
subscription.cancelledA subscription was cancelled
pingTest event sent from the dashboard.

Request format

Headers

POST /hooks/newhost HTTP/1.1
Content-Type: application/json
User-Agent: NewHost-Webhooks/1.0
X-NewHost-Event: deployment.succeeded
X-NewHost-Delivery: clx8dlv0001
X-NewHost-Signature: t=1790000000,v1=5d41402abc4b2a76b9719d911017c592...

Body

{
  "id": "evt_3f9d2c1ab4e5f6a7b8c9d0e1",
  "type": "deployment.succeeded",
  "created": "2026-09-24T10:17:42.000Z",
  "data": { "applicationId": "clx8app0001", "deploymentId": "clx8dep0001", "commitSha": "9f2c1ab" }
}

id is the same for every endpoint that receives the event and across retries, so use it to ignore duplicates. X-NewHost-Delivery identifies one delivery attempt.

Verifying signatures

X-NewHost-Signature holds a Unix timestamp t and v1, the hex HMAC-SHA256 of t + "." + raw body keyed with your endpoint's secret. Recompute it over the raw request body (before JSON parsing), compare in constant time, and reject timestamps older than five minutes to stop replays.

Node.js (Express)

import crypto from "node:crypto";
import express from "express";

const app = express();
app.post("/hooks/newhost", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("X-NewHost-Signature") ?? "";
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", process.env.NEWHOST_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  const valid = v1 && expected.length === v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
  if (!fresh || !valid) return res.status(400).send("bad signature");

  const event = JSON.parse(req.body);
  // ...handle event.type, using event.id to skip duplicates
  res.sendStatus(200);
});

PHP

$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_NEWHOST_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', $sig['t'] . '.' . $body, getenv('NEWHOST_WEBHOOK_SECRET'));
if (abs(time() - (int) $sig['t']) > 300 || !hash_equals($expected, $sig['v1'] ?? '')) {
    http_response_code(400);
    exit('bad signature');
}
$event = json_decode($body, true);
http_response_code(200);

Python (Flask)

import hmac, hashlib, os, time
from flask import Flask, request, abort

app = Flask(__name__)

@app.post("/hooks/newhost")
def newhost():
    parts = dict(p.split("=", 1) for p in request.headers.get("X-NewHost-Signature", "").split(","))
    body = request.get_data()
    expected = hmac.new(os.environ["NEWHOST_WEBHOOK_SECRET"].encode(), f"{parts.get('t')}.".encode() + body, hashlib.sha256).hexdigest()
    if abs(time.time() - int(parts.get("t", 0))) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(400)
    event = request.get_json()
    return "", 200

Responding and retries

  • Return any 2xx within 10 seconds. Do slow work after responding, e.g. on a queue.
  • Anything else (or a timeout) is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours: six attempts over about 15 hours.
  • Redirects are not followed; point the endpoint at the final URL.
  • After 50 failed deliveries in a row the endpoint is disabled. Fix it, then enable it again in the dashboard.
  • The endpoint page shows recent deliveries with the response we received, and lets you resend any of them.
Events can arrive more than once and out of order. Make handlers idempotent, keyed on the event id.