mxAURA Developer Center

Webhooks (outbound events)

Register an https URL and the events you want. When one happens, mxAURA POSTs a signed JSON payload to it — immediately, then with retries. Your systems react instead of polling.

Events

Event Fires when Payload data
dispatch.sent A campaign message was selected and its properties confirmed written to the contact (HubSpot sends next). This is the same guarantee the HubSpot action contract makes. campaign_id, campaign_name, pool_id, pool_name, pool_type, message_id, message_name, message_position, contact_id, portal_id
dispatch.blocked A workflow reached a campaign that cannot send campaign_id, campaign_name, reason_code (no_sender, no_branding, reauth_required, campaign_disabled), reason, portal_id, contact_id
article.published An article went live in a hub article_id, hub_id, placement_id, published_at
hub.subscribed A new subscription (embed key) was created for a hub hub_id, hub_name, subscription_id, subscriber_account_id, subscriber_name
webhook.test You pressed Send test message

* subscribes to all.

Payload

{
  "id": "6f1c0e2a-…",                 // event id, unique
  "event": "dispatch.sent",
  "created_at": "2026-09-04T03:25:13.490Z",
  "account_id": "bc486e46-…",
  "data": { "campaign_id": "…", "message_name": "Welcome 1", "contact_id": "124303957916", "…": "…" }
}

Headers on every delivery:

Content-Type: application/json
User-Agent: mxAURA-Webhooks/1
X-MXA-Event: dispatch.sent
X-MXA-Delivery: <delivery id — unique per attempt series>
X-MXA-Signature: t=1725420313,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>

Verify the signature

Always verify. Anyone can POST JSON to a URL; only mxAURA knows your secret.

  1. Read X-MXA-Signature; split into t and v1.
  2. Compute HMAC-SHA256(secret, t + "." + rawBody) over the raw request body (before any JSON parsing).
  3. Compare to v1 with a constant-time comparison.
  4. Reject if t is older than five minutes (replay protection).

Node (Express)

import crypto from 'node:crypto';
app.post('/hooks/mxaura', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = Object.fromEntries((req.get('X-MXA-Signature') || '').split(',').map(p => p.split('=')));
  const expected = crypto.createHmac('sha256', process.env.MXA_WEBHOOK_SECRET).update(`${sig.t}.${req.body}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(sig.t)) < 300;
  if (!fresh || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1 || ''))) return res.status(401).end();
  const event = JSON.parse(req.body);
  // handle event.event / event.data …
  res.status(200).end();
});

Python (Flask)

import hmac, hashlib, time, json
from flask import Flask, request, abort
app = Flask(__name__)
@app.post('/hooks/mxaura')
def hook():
    parts = dict(p.split('=', 1) for p in request.headers.get('X-MXA-Signature', '').split(','))
    expected = hmac.new(SECRET.encode(), f"{parts['t']}.".encode() + request.get_data(), hashlib.sha256).hexdigest()
    if abs(time.time() - int(parts['t'])) > 300 or not hmac.compare_digest(expected, parts.get('v1', '')):
        abort(401)
    event = json.loads(request.get_data())
    return '', 200

Cloudflare Worker

export default { async fetch(req, env) {
  const raw = await req.text();
  const sig = Object.fromEntries((req.headers.get('X-MXA-Signature') || '').split(',').map(p => p.split('=')));
  const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(env.MXA_WEBHOOK_SECRET), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
  const mac = [...new Uint8Array(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(`${sig.t}.${raw}`)))].map(b => b.toString(16).padStart(2, '0')).join('');
  if (mac !== sig.v1 || Math.abs(Date.now() / 1000 - Number(sig.t)) > 300) return new Response('bad signature', { status: 401 });
  const event = JSON.parse(raw);
  return new Response('ok');
}};

Delivery, retries, pausing

Ordering and duplicates

Deliveries are not guaranteed in order (retries reorder them). Treat id as the deduplication key: if you have seen it, ignore it. An event is never delivered twice as a distinct id, but a retry of the same delivery carries the same id and X-MXA-Delivery.

Endpoint requirements

Managing endpoints

In the app: Account → API → Webhooks (add, edit events, send test, delivery log, pause/resume, rotate secret, remove). Over the API: the webhooks family in the resource reference.

Make, Zapier, n8n

Each of these gives you a "catch webhook" URL. Paste it as the endpoint, choose the events, send a test, and map data.* into the rest of the scenario. The signature can be verified in a code step if the platform supports one; if not, at minimum keep the URL secret and pause the endpoint if you see traffic you did not expect.