Webhook adapter

Last updated

With the webhook adapter, Hetscale creates the server and your own pipeline deploys to it. Each new node boots from the snapshot of your seed. Hetscale sends your endpoint a signed node.created event, your pipeline deploys the current release and reports the node ready, and only then does Hetscale add it to your load balancer. Traffic opens when the load balancer’s health check passes.

How do I switch a node group to the webhook adapter?

On the group’s Deployment page, choose your pipeline and enter the endpoint URL. The URL must use https and resolve to public addresses, without a password. Its host is a public address or a plain host name of letters, digits, hyphens and dots, with an international name in its xn-- form. Hetscale shows the signing secret once; store it where your receiver can read it. The switch is refused while a Rehearsal runs, and servers already running keep what they run. Switching back to the snapshot keeps the URL and the secret.

What is on a new node when the event arrives?

The node boots from the same snapshot of your seed that the snapshot adapter uses, so the runtime and everything on the seed’s disk, .env files included, are already there; a snapshot cleanup never deletes that snapshot. On its first boot, one fixed cloud-init step removes Coolify’s monitoring container, Sentinel, if the snapshot holds it; the step carries no secret, and nothing else of Hetscale’s runs on the node (Coolify). Hetscale sends node.created once the server runs and a boot grace of 30 seconds has passed. The event names the node with its IPs, the group with the generation the node was created for, the release to deploy and the deadline. The event reference lists every field.

How do I verify a delivery?

Every attempt carries four headers: X-Hetscale-Delivery, X-Hetscale-Event, X-Hetscale-Timestamp and X-Hetscale-Signature. The signature is v1=<hex>, an HMAC-SHA256 over <timestamp>.<body> with your secret. Accept the delivery when one v1 value matches, refuse a timestamp older than five minutes, and store the delivery_ids you have seen: a retry repeats the same body and id.

After you rotate the secret, the old one keeps signing next to the new one for a day, so you can switch your receiver at any moment of that day.

What counts as delivered, and what if my endpoint is down?

Any 2xx answer within 10 seconds delivers. Anything else is a failed attempt: another status, a redirect (Hetscale follows none), a failed connection or an address outside the public internet. Failed attempts are retried after 10 s, 30 s, 1, 2, 5, 10 and 20 minutes: eight attempts over about 38 minutes. After the last one the delivery is given up, the node’s deploy fails and the group’s timeline says why.

A URL saved before these host rules that they refuse is not sent to: its attempts fail as “URL not accepted” until a member enters a URL the rules take.

How does my pipeline report the node?

With an API token that has the nodes:report ability, send back the release and the generation that node.created named:

curl -X POST https://api.hetscale.com/v1/nodes/<node-id>/ready \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"release": "<release-id>", "generation": <generation>}'

The ready report is required: without it the node never joins your load balancer, and it is destroyed at the readiness deadline (default 15 minutes); three failures in a row pause the group. The Rehearsal runs through the adapter, so a pipeline that does not report shows up there, before Live, as “the deploy pipeline never reported the server ready”. Set Readiness deadline on the Go Live page before the Rehearsal when your queue and deploy take longer than 15 minutes. If your pipeline deploys over SSH as root, pick the project’s SSH keys on the group page before the Rehearsal: without one, the new server’s root password is expired and every scripted SSH command as root is refused, so the deploy fails. Step 3 of the quickstart has the details. A failed report with a reason stops the node at once. No report opens traffic on its own: the load balancer’s health check does.

How do I roll out a new release?

With a token that has the releases:write ability, send POST /v1/groups/<group-id>/releases with the ref your pipeline deploys, such as an image digest; :latest is refused. Hetscale sends group.release_changed, and every later node.created names the new release. Running nodes keep the release they were deployed with until they are replaced: with Replace older servers on the group’s Servers page, or by your pipeline with POST /v1/groups/<group-id>/rollout and a token that has the rollouts:write ability. Nothing starts by itself after a new release.

A pipeline that deploys to running nodes in place reports each one with POST /v1/nodes/<node-id>/deployed and {"release": "<release-id>"}, with a token that has the nodes:report ability. The node then counts as current and is no longer listed or shown as older, and the group’s timeline says “Deployed in place”. The report is taken for a serving node Hetscale created in a webhook group: a 409 names a group on another adapter (not_webhook_group), the seed (node_not_managed) or a node that does not serve (node_not_active), and a 422 a release the group does not have (release_unknown). The same release again changes nothing.

How does my pipeline hold the group during its own deploy?

With the deploy lock. Send POST /v1/groups/<group-id>/lock with a TTL of 60 to 7,200 seconds and a reason of 1 to 200 characters, such as {"ttl": 1800, "reason": "deploy 4711"}, with a token that has the groups:lock ability (“Lock groups”) of a member who may operate the group (an operator, an admin or the owner). The answer names locked_until. Calling it again sets a new end, now plus the TTL, and DELETE /v1/groups/<group-id>/lock releases it at once, also when none is held. The group must be Live or paused; otherwise the answer is 409 not_live.

While the lock holds, Hetscale makes no scaling decision, out or in, opens no schedule window, moves no server back from a fallback type and adds no server to reach the desired count, and a rollout waits until the lock ends. It never holds healing: a failed server is still replaced, and the replacement’s node.created reaches your endpoint during the lock. A server a scale-in released before the lock still leaves inside its paid hour. At its end the lock expires by itself, so a pipeline that dies cannot hold the group for longer than its TTL. The lock is not exclusive between pipelines: any token with the ability extends or releases it, so a pipeline serialises its own deploys, for example with GitHub’s concurrency.

The group’s Overview, Servers and Deployment pages show “Deploy lock until ‹time› ‹zone›” with the token that holds it and its reason, and say that scaling, schedules and replacing older servers wait while a failed server is still replaced. A member who may change the group ends it early with Release lock. The panel never takes a lock; a pause holds a group by hand. The timeline records “Deploy lock held”, “Deploy lock released” and “Deploy lock expired”.

The host list for the deploy comes from the inventory: GET /v1/groups/<group-id>/nodes, with a token that has the nodes:read ability (“Read nodes”), returns the group and its current servers, each with its id, class, phase, IP addresses, location, server type, release and build id, and older when it runs an older release or template than new servers get. ?phase=active keeps the serving ones; there is no paging. The seed (class: adopted) is listed and is never older. GitHub Actions and Kamal put these calls together. From a GitHub Actions workflow, the Hetscale Deploy action makes each of them one step.

What does this not cover?

  • GitHub’s workflow_dispatch needs a GitHub token, and deliveries carry only Hetscale’s headers, so it takes a small relay that verifies the signature and calls the workflow: the GitHub Actions page has one.
  • A clean OS image instead of the seed’s snapshot: nodes boot the seed’s snapshot for now.
  • The drain: Hetscale sends node.terminating before a node leaves, and the drain does not wait for your pipeline.

The GitHub Actions and Kamal pages put this flow into recipes, the GitHub Actions case shows it for a CI job that deploys over SSH, and the API reference lists every endpoint.

See what Hetscale would have done with your real data — connect read-only, get your report in minutes.

Connect read-only