Webhooks
A webhook is an HTTP callback. When a Git ref moves, Amendable sends an HTTP POST with JSON to an HTTPS URL you run. You do not poll the API. Amendable tells you. This is the same idea as GitHub webhooks: a signed POST on ref changes, then your server clones and does the work.
Hooks here are account-level. One URL receives events from every repository you own. You do not register a hook per repo. Amendable stores Git and does not run your jobs. There is no hosted GitHub Actions or any other CI. The webhook is how a runner, a build job, or a product you ship finds out that a push happened.
When you need one
Section titled “When you need one”You need a webhook when some other process must react to a Git change and that process is not the one that ran git push.
Skip it if you only clone and push from a laptop. Skip it if an agent pushes and then keeps working in the same session. In those cases the pusher already knows the ref moved.
Register one when a separate system should start work:
- CI on your runners. On
push, clonerepository.clone_urlatafterand run tests. Point GitHub Actions or any other runner at a receiver you host, or have that receiver enqueue the job. - Build artifacts from Git. On
createof a tag, orpushtomain, clone the tree and produce whatever you ship: packages, images, previews, exports. MakerRepo is one example (CAD manufacturing). - A Git-backed product. On
push, refresh derived data and UI. BeanHub is one example. More on that: Build a Git product. - Handoff between agents. Agent A pushes to a scratch repo. The webhook starts agent B or a queue job so B does not poll.
Create the webhook once at account setup, not per repository. It already fans out.
What happens
Section titled “What happens”- A Git ref moves (
push,create, ordelete), or you send a ping. - Amendable POSTs JSON to each active HTTPS URL on your account.
- Your endpoint verifies
X-Amendable-Signature(HMAC-SHA256 of the raw body) and replies 2xx quickly. - You clone and do the job asynchronously. Timeouts and failed deliveries are under Delivery behavior.
Events
Section titled “Events”| Event | When |
|---|---|
push |
A branch or tag tip moved, including the first commit on a new ref |
create |
A branch or tag was created (before is 40 zeros) |
delete |
A branch or tag was deleted (after is 40 zeros) |
ping |
Synthetic. Use Ping or POST /v1/webhooks/{id}/ping |
repository |
Reserved. Do not rely on it yet |
Creating a branch emits create and push.
Create
Section titled “Create”The signing secret is generated by Amendable. You cannot set it. It is returned once on create.
Settings → Webhooks. Create, ping, and inspect deliveries there. Open a webhook to see Recent deliveries (status, HTTP code, attempt count, error).
curl -sS -X POST https://api.amendable.io/v1/webhooks \ -H "Authorization: Bearer $AMENDABLE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://ci.example.com/hooks/amendable", "events": ["push", "create", "delete", "ping"], "active": true }'Needs API_WEBHOOKS or ALL. Max 20 webhooks per user. URL must be HTTPS. Private, loopback, and Amendable’s own hosts are rejected.
amendable webhook create https://ci.example.com/hooks/amendable \ --events push,create,delete,pingSave the printed secret.
amendable webhook ping 22222222-2222-2222-2222-222222222222amendable webhook deliveries 22222222-2222-2222-2222-222222222222 --include-payload --jsonHeaders
Section titled “Headers”Every delivery includes:
| Header | Value |
|---|---|
Content-Type |
application/json |
X-Amendable-Event |
push, create, delete, or ping |
X-Amendable-Event-Id |
Event UUID |
X-Amendable-Delivery |
Delivery UUID |
X-Amendable-Signature |
Hex HMAC-SHA256 of the raw body using the webhook secret |
Verify the signature
Section titled “Verify the signature”import hashlibimport hmac
def valid(secret: str, body: bytes, signature: str) -> bool: expected = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature)Reply 2xx after a valid signature. A non-2xx status, including 401 on a bad HMAC, is retried.
import crypto from "node:crypto";
export function valid(secret, body, signature) { const expected = crypto.createHmac("sha256", secret).update(body).digest("hex"); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));}mac := hmac.New(sha256.New, []byte(secret))mac.Write(body)expected := hex.EncodeToString(mac.Sum(nil))hmac.Equal([]byte(expected), []byte(signature))A local HTTP listener can use this check while you develop. The delivery URL you register must be https. Production rejects http, localhost, and private IPs.
Payload (push)
Section titled “Payload (push)”Enough to clone and check out after:
{ "id": "event-uuid", "type": "push", "ref": "refs/heads/main", "ref_name": "main", "ref_type": "branch", "before": "0000000000000000000000000000000000000000", "after": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "created": true, "deleted": false, "commits": [], "head_commit": { "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, "tag": null, "repository": { "id": "repo-uuid", "name": "my-repo", "full_name": "demo/my-repo", "default_branch": "main", "html_url": "https://amendable.io/r/demo/my-repo", "clone_url": "https://amendable.io/r/demo/my-repo.git", "owner": { "id": "user-uuid", "username": "demo" } }}Full field list: Webhook payloads.
Delivery behavior
Section titled “Delivery behavior”Each delivery is tried up to 8 times. Each attempt waits 30 seconds for connect plus response. HTTP 2xx is success. Amendable then stops.
These failures retry. Waits between attempts are 8s, 16s, 32s, 64s, 128s, 256s, then 512s (about 17 minutes of backoff if every attempt fails):
- Timeout or other transport error
- DNS lookup failure
- Any HTTP status that is not 2xx (including 3xx, 4xx, and 5xx)
After the 8th failed attempt the delivery is FAILED. It does not retry again. A later git event creates a new delivery.
These fail on the first attempt, with no retry:
- The webhook is inactive or deleted
- The URL is blocked as unsafe (private, loopback, or Amendable’s own hosts)
- An unexpected worker error
Return 2xx quickly and do the clone or job asynchronously. Redirects are not followed.
Deliveries in the UI
Section titled “Deliveries in the UI”Settings → Webhooks lists each endpoint and its last delivery status.

Open a webhook. Recent deliveries shows when, event, status (SUCCESS, FAILED, or PENDING), HTTP status, attempt count, and error. Details has the error text, the response body, and the JSON payload. Use Ping on that page to test without a git push.

The same rows are on GET /v1/webhooks/{id}/deliveries (needs API_WEBHOOKS or ALL). The CLI prints them with amendable webhook deliveries.
Ping even if ping is not in events. The ping endpoint force-delivers to that webhook only.