A webhook turns Amóni from something you poll into something that calls you. Subscribe an HTTPS endpoint once, and every operation on your account, a power cycle, a reinstall, a rebuild, a reverse-DNS change, a blackhole, an order, arrives there as a signed HTTP POST the moment it finishes. It is how a change made by anyone on the account, in the portal or over the API, reaches your own systems without a script sitting in a loop asking “is it done yet”.
Subscribe
In the portal, open Account, the API Keys tab, and find Webhooks below your keys. Add Webhook, give it an HTTPS URL, and choose which events to receive; leave the events unticked to get all of them.


When you save, a signing secret is shown once. Store it with your endpoint's configuration; it is what proves a delivery came from us. The two events that are delivered are operation.succeeded and operation.failed: one fires when any operation on your account finishes cleanly, the other when it fails.
To subscribe over the API instead, POST /v1/webhooks with a webhooks.write key. The URL must be a public HTTPS address, so an internal or loopback host is refused.
curl -s -X POST https://api.amoni.app/v1/webhooks \
-H "Authorization: Bearer nr_live_..." -H "Content-Type: application/json" \
-d '{"url": "https://ops.example.com/hooks/amoni", "events": ["operation.failed"]}'
{ "success": true, "data": { "id": 2, "secret": "VuBtPOMLGz...Waa5" } }
The secret comes back only on this call. An empty or omitted events array subscribes to every event.
What your endpoint receives
Each delivery is a POST with Content-Type: application/json and a signature header. The body is the event and the operation that caused it:
POST /hooks/amoni HTTP/1.1
Content-Type: application/json
X-Netrouting-Signature: sha256=9f8c1e...c4
{
"event": "operation.succeeded",
"created_at": "2026-09-22T13:07:41+00:00",
"data": {
"id": 51,
"type": "server.reinstall",
"resource_type": "server",
"resource_id": 955,
"status": "succeeded",
"message": "Reinstall completed on web1.example.com"
}
}
The data block is the operation: its id, its type (server.reinstall, server.power, vm.rebuild, ip.rdns, network.blackhole, order.place and so on), the resource it touched, and the outcome. To read more about it later, call GET /v1/operations/{id}.
Verify the signature
Anyone can POST to a public URL, so check the signature before you trust a delivery. Compute an HMAC-SHA256 of the raw request body with your signing secret, and compare it to the hex in the X-Netrouting-Signature header after the sha256= prefix. Compare in constant time, and do it against the bytes as received, before any JSON parsing or re-encoding.
# Python / Flask
import hmac, hashlib
from flask import Flask, request, abort
SECRET = b"VuBtPOMLGz...Waa5" # your signing secret
app = Flask(__name__)
@app.post("/hooks/amoni")
def hook():
sent = request.headers.get("X-Netrouting-Signature", "")
expected = "sha256=" + hmac.new(SECRET, request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(sent, expected):
abort(401)
event = request.get_json()
# act on event["event"] / event["data"]; return 2xx to acknowledge
return "", 204
// Node / Express (raw body required for the HMAC)
const express = require("express"), crypto = require("crypto");
const SECRET = "VuBtPOMLGz...Waa5";
const app = express();
app.post("/hooks/amoni", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const sent = req.get("X-Netrouting-Signature") || "";
if (sent.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(expected))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString());
res.sendStatus(204);
});
Return any 2xx status to acknowledge. Anything else, or no response, counts as a failure.
Testing, retries and failures
Test next to a subscription sends a sample operation.succeeded to your endpoint through the same path a real event takes, so a green test proves your signature check and your reachability at once. Deliveries shows the recent attempts and the status code each got back; over the API, GET /v1/webhooks/{id}/deliveries returns the same log.
A delivery that does not get a 2xx is retried a few times with a growing gap between attempts. If an endpoint fails ten times in a row it is deactivated, and the portal shows the failure count so you can see why. A dead endpoint therefore stops earning retry traffic; fix it and add the subscription again.
Questions we get
- Which events exist?
operation.succeededandoperation.failed. Every write on the account is an operation, so between them they cover power, reinstall, rescue, rebuild, reverse DNS, blackhole and orders. - Can I have more than one endpoint? Yes. Add as many as you need; each gets its own secret and its own delivery log.
- I lost the signing secret. It is shown only at creation. Delete the subscription and add it again to get a new secret.
- My endpoint went down and the subscription stopped. Ten consecutive failures deactivate it. Bring the endpoint back and re-add the webhook.
- Do webhooks replace monitoring? No. These report operations you or your tools triggered. For up or down alerts on a service, see uptime monitoring.
Still stuck?
Open a support ticket with the endpoint URL and a delivery's status code from the log. We can see what we sent and what came back.