Adwix AtlasPlatform

Outbound webhooks

Atlas calls YOUR URL when something happens in your workspace — signed, retried, at-least-once. The reverse direction of the lead webhook.

Setup#

In Atlas → Settings → Integrations → Outbound Webhooks (workspace owner, up to 5 endpoints): add your public https:// URL, pick the events, and copy the signing secret (whsec_…) — it's shown once. Then hit Send test: Atlas posts a signed pingevent and shows you your server's response code on the spot.

Events#

FieldAccepted keysNotes
lead.createddata = the leadFires for EVERY entry path — in-app, lead webhook, REST API, Meta Lead Ads import.
lead.stage_changeddata = the lead (current state)Fires on pipeline moves. A move to status "confirmed" is your booking-won signal today.
quote.accepteddata = { quote_id, lead }Customer accepted a quotation; the lead is included so you can act on contact details immediately.
pingdata = { message }Sent by the "Send test" button only.
Planned: dedicated booking.confirmed and payment.received events. Until they ship, lead.stage_changed → status confirmed covers the booking-won moment — and this page will say so honestly until the real events exist.

Delivery format#

POST with Content-Type: application/json and headers X-Atlas-Event, X-Atlas-Delivery (unique id — dedupe on it), X-Atlas-Timestamp (unix seconds) and X-Atlas-Signature. Lead objects use the exact same shape as the REST API.

Example delivery — lead.created
{
  "event": "lead.created",
  "created_at": "2026-07-21T10:15:00.000Z",
  "data": {
    "id": "3f7c2b9e-…",
    "name": "Rahul Sharma",
    "phone": "+91 98765 43210",
    "email": "rahul@example.com",
    "destination": "Bali",
    "budget": 150000,
    "pax": 2,
    "travel_date": "12 Aug 2026",
    "status": "new",
    "source": "website",
    "score": 0,
    "notes": "",
    "follow_up_date": null,
    "tags": [],
    "created_at": "2026-07-21T10:14:59.000Z",
    "updated_at": "2026-07-21T10:14:59.000Z"
  }
}

Verifying signatures#

The signature is v1=HMAC_SHA256(secret, `{timestamp}.{raw_body}`) in hex. Always verify against the raw request body — re-serialising the JSON can reorder keys and break the match.

Node.js verification
import { createHmac, timingSafeEqual } from 'crypto'

// Express example — use the RAW body string, not re-serialized JSON.
app.post('/atlas-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const secret = process.env.ATLAS_WEBHOOK_SECRET   // whsec_… from Settings
  const ts = req.header('X-Atlas-Timestamp')
  const sig = (req.header('X-Atlas-Signature') || '').replace('v1=', '')

  const expected = createHmac('sha256', secret)
    .update(`${ts}.${req.body.toString()}`)
    .digest('hex')
  const valid = sig.length === expected.length &&
    timingSafeEqual(Buffer.from(sig), Buffer.from(expected))

  // Reject stale timestamps too (replay guard) — e.g. older than 5 minutes.
  if (!valid || Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
    return res.status(401).end()
  }

  const body = JSON.parse(req.body.toString())
  // …handle body.event / body.data. Respond fast; do slow work async.
  res.status(200).end()
})

Retries & guarantees#

— Any 2xx from you counts as delivered. Respond within 5 seconds — acknowledge fast, process async.
— Non-2xx or timeout → retries with backoff: ~2 min, 10 min, 30 min, 2 h, 6 h (5 attempts total), then the delivery is marked failed.
— Delivery is at-least-once: rare duplicates are possible — dedupe on X-Atlas-Delivery.
— Order is not guaranteed across events; use the payload's timestamps.
— Delivery history is retained for 30 days. The Settings card shows each endpoint's last success and last failure.