Developers
Webhooks and API
When something happens on an AOS Sites website (a new lead, a finished form, a publish), we post a signed JSON message to the addresses you choose. Connect Zapier or Make with no code, or your own system with a few lines.
Overview
- • Add endpoints in Settings → Integrations (the owner or an admin, on any paid plan).
- • Every message is signed with that endpoint's secret, so you can tell it came from us.
- • If your endpoint doesn't answer with a 2xx, we try again for about 24 hours.
- • Each event has an id. A retry sends the same id, so you can ignore one you've already handled.
Events
| Event | Sent when |
|---|---|
lead.created | Someone gets in touch through your site: a form, AOS's chat or a call. |
lead.updated | A lead moves to contacted, booked, won or lost, or its job value changes. |
form.submitted | Someone finishes one of your smart forms, with every answer. |
booking.created | Someone books or requests a time on your booking calendar. |
booking.updated | A booking is confirmed, moved, canceled, or marked done or no-show. |
site.published | Your changes go live. |
lead.created
{
"id": "3f0c2a8e-6b1d-4c2e-9f7a-5d4e3c2b1a00",
"type": "lead.created",
"created_at": "2026-11-12T15:04:05.000Z",
"account_id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
"test": false,
"data": {
"id": "6f1d0c52-2b7a-4c1e-9a43-1f2e3d4c5b6a",
"name": "Jordan Lee",
"email": "jordan@example.com",
"phone": "+16155550123",
"message": "Do you do roof inspections after hail?",
"channel": "form",
"status": "new",
"source": "google_search",
"landing_path": "/",
"page_path": "/contact",
"device": "mobile",
"created_at": "2026-11-12T15:04:05Z"
}
}lead.updated
{
"id": "3f0c2a8e-6b1d-4c2e-9f7a-5d4e3c2b1a00",
"type": "lead.updated",
"created_at": "2026-11-12T15:04:05.000Z",
"account_id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
"test": false,
"data": {
"id": "6f1d0c52-2b7a-4c1e-9a43-1f2e3d4c5b6a",
"name": "Jordan Lee",
"email": "jordan@example.com",
"status": "booked",
"previous_status": "contacted",
"job_value_cents": 840000,
"updated_at": "2026-11-13T09:30:00Z"
}
}form.submitted
{
"id": "3f0c2a8e-6b1d-4c2e-9f7a-5d4e3c2b1a00",
"type": "form.submitted",
"created_at": "2026-11-12T15:04:05.000Z",
"account_id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
"test": false,
"data": {
"id": "0b8e7d6c-5a4b-4f3e-8d2c-1b0a9f8e7d6c",
"form_name": "Free estimate",
"answers": {
"service": "Roof repair",
"timeline": "This month",
"address": "12 Oak St"
},
"submitted_at": "2026-11-12T15:04:05Z",
"lead": {
"id": "6f1d0c52-2b7a-4c1e-9a43-1f2e3d4c5b6a",
"name": "Jordan Lee",
"email": "jordan@example.com"
}
}
}booking.created
{
"id": "3f0c2a8e-6b1d-4c2e-9f7a-5d4e3c2b1a00",
"type": "booking.created",
"created_at": "2026-11-12T15:04:05.000Z",
"account_id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
"test": false,
"data": {
"id": "7c6b5a49-3e2d-4c1b-8a09-f8e7d6c5b4a3",
"type": "Free estimate",
"location": "visit",
"start_at": "2026-11-14T15:00:00Z",
"end_at": "2026-11-14T15:30:00Z",
"timezone": "America/Chicago",
"status": "booked",
"name": "Jordan Lee",
"email": "jordan@example.com",
"phone": "+16155550123",
"source": "site",
"created_at": "2026-11-12T15:04:05Z"
}
}booking.updated
{
"id": "3f0c2a8e-6b1d-4c2e-9f7a-5d4e3c2b1a00",
"type": "booking.updated",
"created_at": "2026-11-12T15:04:05.000Z",
"account_id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
"test": false,
"data": {
"id": "7c6b5a49-3e2d-4c1b-8a09-f8e7d6c5b4a3",
"type": "Free estimate",
"start_at": "2026-11-15T16:00:00Z",
"status": "booked",
"previous_status": "booked",
"previous_start_at": "2026-11-14T15:00:00Z",
"name": "Jordan Lee",
"updated_at": "2026-11-13T09:30:00Z"
}
}site.published
{
"id": "3f0c2a8e-6b1d-4c2e-9f7a-5d4e3c2b1a00",
"type": "site.published",
"created_at": "2026-11-12T15:04:05.000Z",
"account_id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
"test": false,
"data": {
"site_id": "3c2b1a09-8f7e-4d6c-9b5a-4e3d2c1b0a98",
"slug": "vanguard-roofing",
"url": "https://vanguard-roofing.site.agency-os.ai",
"published_at": "2026-11-12T15:04:05Z"
}
}What we send
A POST with a JSON body (above) and these headers. Answer with any 2xx within 10 seconds; do slow work after you answer.
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: AOS-Sites-Webhooks/1.0
AOS-Event: lead.created
AOS-Event-Id: 3f0c2a8e-6b1d-4c2e-9f7a-5d4e3c2b1a00
AOS-Delivery: 9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a
AOS-Signature: t=1794495845,v1=5b7e…Check the signature
Each endpoint has its own secret (whsec_…), shown when you add it and from its Secret button. Compute an HMAC-SHA256 of the timestamp, a dot and the raw body, and compare. Reject anything older than 5 minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
// AOS-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
export function fromAosSites(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=", 2)));
const t = Number(parts.t);
if (!parts.v1 || !Number.isFinite(t)) return false;
if (Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes: a replay
const want = Buffer.from(createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"));
const got = Buffer.from(parts.v1);
return want.length === got.length && timingSafeEqual(want, got);
}Retries
A timeout, 408, 429 or 5xx is tried again, 8 tries in all over about 24 hours: after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 6 hours. Any other 4xx, or a redirect, isn't retried (redirects aren't followed: use the final address). Answering 410 Gone switches the endpoint off, and 25 failed messages in a row do the same. Settings → Integrations keeps a 30-day log with every answer, and a “Try again now” button.
Zapier and Make
- In Zapier, start a Zap with Webhooks by Zapier → Catch Hook (in Make: Webhooks → Custom webhook) and copy the address it gives you.
- In AOS Sites, go to Settings → Integrations, add that address and pick the events.
- Press Send a test; Zapier or Make now sees every field to map.
Building your own Zapier app, or a tool that subscribes for itself? Use the REST hooks API below with an API key.
API
Base address https://site.agency-os.ai/api/v1. Make a key in Settings → Integrations and send it as Authorization: Bearer aos_live_…. Answers are { ok: true, data } or { ok: false, code, message }; 120 requests a minute per key.
# Who the key belongs to (use it to test a connection)
curl https://site.agency-os.ai/api/v1/me -H "Authorization: Bearer $AOS_KEY"
# Subscribe: post this event to target_url → 201 { ok, data: { id } }
curl -X POST https://site.agency-os.ai/api/v1/hooks -H "Authorization: Bearer $AOS_KEY" \
-H "Content-Type: application/json" \
-d '{"target_url":"https://hooks.zapier.com/hooks/standard/123/abc","event":"lead.created"}'
# This key's subscriptions
curl https://site.agency-os.ai/api/v1/hooks -H "Authorization: Bearer $AOS_KEY"
# Unsubscribe
curl -X DELETE https://site.agency-os.ai/api/v1/hooks/<id> -H "Authorization: Bearer $AOS_KEY"
# Recent events in the webhook shape (Zapier's sample data), a JSON array
curl "https://site.agency-os.ai/api/v1/samples?event=lead.created" -H "Authorization: Bearer $AOS_KEY"Revoking a key ends its subscriptions too. There's no write access to leads or the site through the API.