Webhooks
Register HTTPS endpoints in Settings → API Keys to receive real-time events. Every delivery is signed with HMAC-SHA256 and retried up to 3 times with exponential backoff.
Events
campaign.builtFired when an AI campaign build finishes and the campaign is ready.
{
"data": {
"campaignId": "cmp_123",
"name": "Spring Sale — Prospecting",
"platform": "meta",
"status": "active",
"adSetCount": 3,
"adCount": 6
}
}campaign.budget_adjustedFired when a campaign or ad set budget is changed (manually or by the optimizer).
{
"data": {
"campaignId": "cmp_123",
"previousBudget": 50,
"newBudget": 75,
"currency": "USD",
"source": "optimizer",
"reason": "ROAS above target — scaling spend"
}
}creative.readyFired when an AI-generated creative finishes processing and is available.
{
"data": {
"creativeId": "crt_123",
"campaignId": "cmp_123",
"type": "image",
"fileUrl": "https://cdn.lazyads.ai/creatives/crt_123.png",
"predictedRoas": 2.4
}
}optimization.action_takenFired when the autonomous optimizer takes an action on a campaign.
{
"data": {
"campaignId": "cmp_123",
"action": "pause_ad",
"entityId": "ad_456",
"detail": "CPA 3x above target for 5 days"
}
}Request format
Each delivery is a POST with a JSON body and these headers:
X-LazyAds-SignatureHex HMAC-SHA256 of `{timestamp}.{rawBody}` using your endpoint secretX-LazyAds-TimestampUnix timestamp (seconds) the signature was computed atX-LazyAds-EventThe event type, for routing without parsing the body{
"id": "whd_abc123",
"type": "campaign.built",
"createdAt": "2026-06-24T18:00:00.000Z",
"data": {
"campaignId": "cmp_123",
"name": "Spring Sale — Prospecting",
"platform": "meta",
"status": "active",
"adSetCount": 3,
"adCount": 6
}
}Verifying signatures
Always verify the signature against the raw request body before acting on an event. Reject deliveries whose timestamp is more than a few minutes old to prevent replays.
import crypto from 'crypto';
// Express example — verify the signature before trusting the payload.
app.post('/webhooks/lazyads', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.header('X-LazyAds-Signature');
const timestamp = req.header('X-LazyAds-Timestamp');
const rawBody = req.body.toString('utf8');
const expected = crypto
.createHmac('sha256', process.env.LAZYADS_WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const valid =
signature &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) return res.status(400).send('invalid signature');
const event = JSON.parse(rawBody);
// handle event.type ...
res.sendStatus(200);
});Retries & delivery logs
A delivery is considered successful on any 2xx response. Non-2xx responses and timeouts are retried up to 3 attempts. Endpoints that fail repeatedly are automatically disabled — re-enable them from settings once fixed. Recent delivery attempts and their status codes are available per endpoint.
Zapier & n8n (REST Hooks)
Automation tools can self-register subscriptions with just an API key — no in-app setup needed. This follows the standard Zapier REST Hook pattern, and works the same in n8n, Make, or any HTTP node.
POST /api/v1/webhooksSubscribe — body { url, events? }. Returns the signing secret once.GET /api/v1/webhooksList your active subscriptions.DELETE /api/v1/webhooks/{id}Unsubscribe by id (or DELETE /api/v1/webhooks?url=…).GET /api/v1/webhooks/eventsEvent catalog with sample payloads for field mapping.# Subscribe (what Zapier/n8n calls to register a target URL)
curl -X POST https://app.lazyads.ai/api/v1/webhooks \
-H "Authorization: Bearer $LAZYADS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.zapier.com/hooks/catch/123/abc",
"events": ["campaign.built", "creative.ready"]
}'
# → 201 { "data": { "id": "whe_…", "secret": "whsec_…", "events": [...] } }
# Unsubscribe
curl -X DELETE "https://app.lazyads.ai/api/v1/webhooks/whe_…" \
-H "Authorization: Bearer $LAZYADS_API_KEY"In Zapier, use a “Catch Hook” / REST Hook trigger and point the subscribe step at POST /api/v1/webhooks with the polling/target URL Zapier provides; map fields from GET /api/v1/webhooks/events. In n8n, use a Webhook node as the target and register its URL with the same endpoint. The API key needs the webhooks scope.