Siren

Headless Attribution

Track a referred visit and pass the Siren opportunity ID between your frontend and a WordPress installation running Siren.

Requires Siren Essentials

Last updated: April 30, 2026

In a headless Siren install, the frontend and the WordPress backend share one piece of state: the opportunity ID that links a visit to a collaborator. This page covers the canonical way to mint that ID when a referred visitor arrives, persist it across page loads, and send it back at conversion time. Everything here ships in the core Siren plugin on the Essentials tier and above.

The flow

Referral lands

Visitor arrives on the headless frontend with ?ref=<collaborator>.

Frontend reports the visit

POST to the site-visited event endpoint with the collaborator alias.

Siren returns the opportunity ID

200 OK with the new ID in the X-Siren-OID response header.

Frontend persists the ID

Store in a first-party cookie or local storage for the attribution window.

Visitor converts

Frontend sends X-Siren-OID back as a request header on the conversion call.

Pipeline runs

Siren resolves the opportunity and runs the standard conversion pipeline.

End-to-end this is about fifteen lines of code in a typical frontend:

// On the landing page, when ?ref=ABC123 is present.
// Re-POST whenever a ref is present so Siren's resolver can decide whether
// to merge with an existing opportunity or create a new one.
const ref = new URLSearchParams(location.search).get("ref");
if (ref) {
  const existingOid = document.cookie.match(/siren_oid=([^;]+)/)?.[1];
  const res = await fetch("/wp-json/siren/v1/event/site-visited", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...(existingOid && { "X-Siren-OID": existingOid }),
    },
    body: JSON.stringify({ collaboratorId: `tracking:${ref}` }),
  });
  const oid = res.headers.get("X-Siren-OID");
  // Match max-age to your program's attribution window
  // (/documentation/general/cookie-duration).
  if (oid) document.cookie = `siren_oid=${oid}; path=/; max-age=2592000; Secure; SameSite=Lax`;
}

// On the conversion call (checkout, form submit, etc.).
// Omit the header entirely when there's no OID rather than sending an empty string.
const oid = document.cookie.match(/siren_oid=([^;]+)/)?.[1];
fetch("/api/checkout", {
  method: "POST",
  headers: { ...(oid && { "X-Siren-OID": oid }) },
  body: payload,
});

The cookie can’t be HttpOnly, because the conversion-time fetch needs to read it. Keep Secure and SameSite=Lax in place so the cookie isn’t transmitted over HTTP and is sent on top-level navigations only.

That’s the whole shape. The rest of this page is what each step actually does.

Step 1: Track the visit

The frontend posts the visit to the /event/site-visited endpoint, passing the collaborator’s tracking alias as collaboratorId. The tracking: prefix tells Siren the value is a referral code, not an internal ID. That means you can read ?ref= straight off the URL and forward it without a reverse lookup. The same alias system handles this on WordPress-rendered sites.

The opportunity ID comes back in the X-Siren-OID response header. Read it client-side and persist it for the next steps.

Step 2: Persist the opportunity ID

The opportunity ID needs to survive across requests and page loads, but it doesn’t need to live forever. Siren’s standard attribution window decides how long it remains valid. Once that window closes, the opportunity expires regardless of what your frontend does.

The recommended pattern is a first-party cookie scoped to your frontend’s domain, with a lifetime that matches your intended attribution window. Local storage works, but it loses the opportunity if the visitor clears site data or switches browsers. Reach for it only if the cookie path is closed off.

If a returning visitor arrives with a new referral parameter, POST another site-visited event with the new collaboratorId and let Siren’s resolver decide whether to merge or create a new opportunity. If you already have an opportunity ID stored, include it as X-Siren-OID on the inbound POST so Siren uses it as the resolution starting point.

Step 3: Send the opportunity ID at conversion time

When the visitor takes a conversion action (checking out, submitting a form, or anything else your program attributes on), your frontend forwards the request to WordPress with the persisted opportunity ID in a custom header:

X-Siren-OID: 4218

Siren’s response interceptor reads the header, resolves the opportunity, and feeds it into the same conversion pipeline that runs for native WordPress traffic. The interceptor also writes the resolved ID back to the response in the same header, so your frontend can refresh its stored value if Siren merged it with another opportunity.

What that looks like in practice depends on what triggers the conversion. Three paths cover the common cases:

  • WooCommerce. The Siren extension fires SaleTriggered itself and picks up the header along with the rest of the request. Attribution lands automatically.
  • Direct conversion creates. Posting to the conversions endpoint requires admin auth, so this has to come from a server-side runtime.
  • Non-WordPress commerce stack. Reporting through /event/sale resolves the same header the same way.

The path into Siren changes. The header contract is constant.

Trust boundary worth understanding

X-Siren-OID is set by the browser on every conversion call. At the network boundary it’s attacker-controlled: anyone can pick an opportunity ID and try to credit a sale to it. The protection isn’t cryptographic verification of the OID itself. Two different things bound the abuse vector:

  • The conversions endpoint requires admin auth, so a stranger can’t manufacture conversions directly through it.
  • Public sale events (/event/sale) get crediting decisions only when the rest of the payload represents real commerce: a real WooCommerce order, a real Stripe charge, etc. An attacker who can fabricate that has bigger problems than headless attribution.

Where this matters most is /event/site-visited. It’s unauthenticated by default and not rate-limited unless the install ships its own EventIngestionRateLimitMiddleware. A scripted caller can iterate referral codes and inflate visit counts. Sites running real money through the public path should plan for rate limiting, bot protection, or fraud review. The plugin gives you the extension point. The abuse posture is the install’s choice.

What failure looks like

Three failures show up on a first build:

  • Unresolvable alias. POST /event/site-visited with collaboratorId: "tracking:DOES_NOT_EXIST" falls through the alias resolver unchanged, then trips the validation middleware (collaborator ID must be numeric or resolve to one) and returns a 4xx with the error in the body. Treat this as “no attribution” rather than retrying.
  • Stale OID at conversion time. If the cookie holds an OID whose attribution window has closed, the conversion still goes through but lands without attribution. The response header in that case carries no replacement value. Surface this in your logs to detect window misconfigurations.
  • CORS preflight refused. OPTIONS requests must be allowed and X-Siren-OID must appear in Access-Control-Allow-Headers. Both are covered in the introduction’s CORS section.

The Siren marketing site you’re reading right now uses this exact pattern. See the REST API launch post for the broader story.