Skip to documentation content
Lifecycle recipes

Receive lifecycle events at a webhook

Each recipe posts one signed JSON event to your endpoint. Your code checks the signature and acts on the fields.

All lifecycle guides · Checked against AXO’s adapters in October 2026.

Before you start

Connect Webhook in AXO first: see Connect Webhook. The integration must be active. In the dashboard, the Install button is off for read-only members.

When AXO sends

A recipe is installed as an ordinary AXO trigger, turned off. Once you turn it on, AXO watches for the moment, waits where the recipe waits, skips people who converted or are in their cooldown, and sends the event. Every lifecycle event needs the person’s marketing consent. A holdout group gets nothing so that the trigger’s lift can be measured. Event names:

  • Abandoned cart: axo.lifecycle.abandoned_cart
  • Abandoned browse: axo.lifecycle.abandoned_browse
  • Abandoned checkout: axo.lifecycle.abandoned_checkout
  • High-intent return: axo.lifecycle.high_intent_return
  • Lapsed visitor: axo.lifecycle.lapsed_visitor

See Create a trigger for cooldowns, holdout and fire history.

Build the receiving side

AXO posts the canonical lifecycle event as JSON to the webhook integration’s URL (custom integrations use the same path). The request has three headers:

  • X-VibeTag-Signature: the hex HMAC-SHA256 of the raw body, keyed by the integration’s secret. Set a secret: without one, AXO signs with your public site id, which anyone with your install snippet can reproduce.
  • X-VibeTag-Trigger: the trigger id.
  • X-VibeTag-Identifier-Type: the identifier type the recipe sends to, such as email.
  1. Build an endpoint at a public HTTPS address. AXO refuses private and loopback addresses.
  2. Verify the signature against the raw body before reading it, as in the example below.
  3. Drop repeats by delivery_id, which stays the same when one moment is sent again. Test sends use ids that start with lcd_test_.
  4. Answer with a 2xx status within 8 seconds. A 408, 429 or 503 answer, or a connection that never opens (refused, DNS or TLS failure, connect timeout), is retried with the same body and delivery_id: up to 5 attempts in all, spaced about 2 minutes, 15 minutes, 1 hour and 4 hours apart, for as long as the recipe’s window setting still allows the send. The fire history shows retrying until one succeeds or the attempts run out. Any other status, or a timeout after the connection opened, is recorded as an error and not retried, since your endpoint may already have acted on it.
  5. In AXO, install the recipe with the webhook as a destination, send a test from the trigger’s Test fire button, then turn the trigger on (Turn on right after install, or Resume on the trigger).
Verify the signature (Node.js)
import crypto from "node:crypto";

// secret: the value set on the webhook integration in AXO.
// Hash the raw request body exactly as received.
function verify(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(signatureHeader || "", "utf8");
  const b = Buffer.from(expected, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// In the handler: reject a bad signature, skip a delivery_id you have
// already seen, then answer 2xx within 8 seconds.

Fields

Top-level fields of the JSON body.

FieldTypeMeaning
schema_versionnumberVersion of this event shape. Currently 1.
event_nametextaxo.lifecycle.<recipe_id>, for example axo.lifecycle.abandoned_cart.
recipe_idtextThe recipe that fired.
recipe_versionnumberThe recipe version the trigger was installed with.
occurred_attextWhen AXO fired the event (ISO 8601, UTC).
fire_idtextAXO’s id for this send attempt.
delivery_idtextThe same on every attempt for one moment and one destination. Use it to drop duplicates. Test sends start with lcd_test_.
trigger_idtextThe AXO trigger the recipe was installed as.
siteobjectThe AXO site: { id, domain }.
profileobjectThe person: { id, identifiers: { email, email_sha256, phone, external_id, anonymous_id } }. Identifiers the person does not have are left out.
consent_basistextAlways marketing. AXO sends lifecycle events only for people with marketing consent.
itemslistLine items or viewed products. See the item fields below.
cart_valuenumberCart value as a number, or null when unknown.
currencytextISO 4217 code such as USD, or null.
resume_urltextA link back to the cart or page. Real sends carry a vt_token that signs the person back in; test sends carry the plain link.
sourceobjectThe moment that started the recipe: { event_type, page_url, occurred_at }.
settingsobjectThe settings the recipe was installed with.

Item fields

Each entry in items has these fields.

FieldTypeMeaning
product_idtextProduct id (the numeric Shopify id for Shopify catalogs).
skutextSKU.
nametextProduct name.
urltextProduct page URL.
image_urltextProduct image URL.
pricenumberPrice as a number.
previous_pricenumberAlways null for now. It will hold the price before a drop once the catalog keeps price history.
currencytextISO 4217 code.
availabilitytextAvailability text from the catalog, such as in_stock.
quantitynumberQuantity in the cart. Null for viewed products.