signalq to pass your consent platform’s current decision to Signals and send business events after a full advertising grant. With a connected Meta dataset, one browser track call supplies the managed Pixel and server event with a shared ID.
Before you begin
- Install the generated Signals pixel once and keep its
initcall before consent and event calls. - Choose a consent source: a supported external signal, an explicit CMP adapter, or the configured Plainrouter fallback card. Verify the actual advertising choices on every page and after changes.
- Connect Meta if you want managed Meta delivery.
- Choose whether the browser, your backend, or Stripe owns each purchase. Follow Avoid duplicate Meta events before combining integrations.
undefined. It does not return the later command’s result. Run examples that read an event ID, identification result, or visitor ID only after the generated script’s load event; register that handler before the script can finish loading. Do not install a second snippet to wait for it.
How does the snippet choose a consent source?
The current v4 snippet sends its arrival first, then uses a queued explicitsignalq("consent", …) decision when present. Otherwise it checks supported TCF and Consent Mode signals during a 1.5-second detection window. A detected supported CMP without a usable decision suppresses the fallback; it does not grant consent. Keep the explicit adapter below when your CMP needs one.
If no external source takes precedence, the snippet can restore the saved Plainrouter choice or display the enabled fallback consent card. Accept all, Reject all, and Customize record different decisions. In Customize, Analytics controls ad_storage, while Marketing controls both ad_user_data and ad_personalization. Advertising events and managed Meta delivery still require all three grants; selecting only Analytics is insufficient.
A later valid external decision dismisses the fallback. The pr_consent cookie stores the Plainrouter choice; it is separate from visitor measurement identifiers. To reopen the card from your own preferences control, use:
Set consent
Connectsignalq to your consent-management platform. Send the current advertising consent state after initialization and whenever the visitor changes it.
granted, denied, or unknown. Missing or unrecognized permissions remain unknown; Plainrouter never upgrades them to a grant.
You can provide one or more supported consent adapters:
When you provide more than one adapter, their derived permission states must agree.
Conversion events, visitor-cookie access, identity transmission, and managed Meta delivery require all three permissions to be
granted. Granting ad_storage alone is insufficient. The identity-free arrival sent once at document initialization is a separate request and does not wait for these permissions. See What happens on initialization.
A complete grant creates or refreshes the first-party identifiers used for session joins and eligible destination delivery. Leaving full consent stops event transmission and clears the current page’s consent-dependent identifiers. Explicit storage denial or withdrawal also discards the in-memory landing context. Send the latest structured state whenever the visitor’s choice changes.
On a returning page, the arrival is sent while your consent platform restores its decision. Plainrouter does not read visitor cookies or send conversion events during that undecided period. Existing cookies are not read or refreshed merely because the snippet initialized.
Connect Cookiebot callbacks
Install Cookiebot on the same page. This adapter uses Cookiebot’s consent properties and browser events. Use it only when your configured marketing choice covers all three Plainrouter advertising permissions. For separate choices, map each permission individually with the structured snapshot above. Place this code once after your generatedinit call. It handles an already-restored choice and later CMP events. Automatic or implied consent stays unknown; only an explicit response can grant permissions.
captured_at records when this adapter reads the current decision. Retain your CMP’s consent record separately. Do not replay purchases from these callbacks: restored consent and multiple CMP events can call the adapter more than once.
For a custom withdrawal button, notify Signals immediately as well as withdrawing in Cookiebot. Attach this function to your site’s existing consent-withdrawal control:
Verify grant, partial consent, and withdrawal
Use a development or test site and inspect its Network panel. Test the installed library, not just the pre-load queue. A successful arrival is a separate initialization check and can appear in every row below.
For a CMP with separate permissions, exercise a partial choice through its UI and confirm the transmitted snapshot preserves it. Do not replace partial choices with the Cookiebot adapter’s shared marketing value.
If the result differs, confirm
init ran first, inspect the actual three permission values, and check for conflicting adapters. Use Verify ingestion to separate a collection problem from consent gating; use Meta delivery checks for destination acceptance.
How landing click IDs are handled
The snippet captures supported landing query parameters in the current document’s memory:fbclid, gclid, gbraid, wbraid, ttclid, and msclkid. Unknown or partial consent keeps this temporary buffer available for a later complete grant. Explicit storage denial or withdrawal discards it.
Click IDs are sent with eligible events only after full consent. They never enter the identity-free arrival payload. The browser no longer reads or writes the legacy pr_click_ids value; it expires that cookie when full consent opens and when an active grant is withdrawn. A new document starts with its own landing context.
The Meta _fbc and _fbp cookies are separate from this retired click-ID map and remain subject to full consent. At event intake, Plainrouter drops invalid or unsupported click-ID entries individually while retaining valid entries. Do not rely on one malformed value to reject an otherwise valid event.
Track an event
Calltrack with an event name and an optional payload:
null when full consent is absent, consent changes stop the request, or collection is not accepted after the permitted attempts. Invalid calls, such as a missing event name or a call before init, can reject the promise.
The public track command generates its own event ID. An event_id in the payload or an extra positional argument does not override it. order_id is purchase data, not the deduplication key.
Send monetary
value as a decimal string, such as "49.00". This avoids floating-point ambiguity.Standard event names
Common names are normalized to Meta’s standard spelling. For example,page_view becomes PageView and initiate_checkout becomes InitiateCheckout.
Frequently used standard events include:
PageViewViewContentSearchAddToCartAddToWishlistInitiateCheckoutAddPaymentInfoPurchaseLeadCompleteRegistrationContactScheduleSubscribe
Payload fields
These convenience fields move into the event’s value data automatically:
You can also provide a
value_data object directly. Other custom fields remain on the event and are available to supported destinations.
Identify a consenting visitor
Callidentify only after your consent policy permits advertising identity use:
- Email is trimmed and lowercased.
- International phone numbers must start with
+and include 8–15 digits after normalization. - Finnish local numbers require
defaultPhoneRegion: "FI"in the initialization configuration. - Identification returns
falseon an insecure page where browser cryptography is unavailable.
Read the visitor ID
getVisitorId returns the current first-party visitor ID after consent is granted. It returns null while consent is pending or denied.
Verify Stripe Checkout settlement
Stripe verified revenue records paid Checkout Sessions in Plainrouter. This is a revenue connection, not a Meta Conversions API integration: do not expectmetadata.visitor_id to join the purchase to a browser visit or trigger Meta delivery. For consented advertising conversions, use the managed browser purchase or backend recipe.
What do I need before connecting Stripe?
You need an active Signals workspace, workspace-owner access to Connections, and permission to configure webhook endpoints for the Stripe account that receives your customers’ payments. Your application must already create Checkout Sessions and handle order fulfillment. This connection is separate from paying for Plainrouter. The supported settlement flow uses Checkout Session events. It does not establish support for every Stripe payment flow or recurring invoice event. Stripe’s fulfillment guide explains delayed payments and why returning to a success URL does not prove payment.Configure the webhook
- Open Connections → Revenue sources → Stripe.
- Copy the workspace’s Webhook endpoint and add it as an endpoint in the intended Stripe account.
- Subscribe to
checkout.session.completed,checkout.session.async_payment_succeeded, andcheckout.session.async_payment_failed. Addrefund.createdandcharge.refundedif you track refunds. - Copy that endpoint’s signing secret into Endpoint signing secret in Plainrouter and choose Save secret. This is the webhook signing secret, not your Stripe API key.
- Confirm the Stripe card shows
connected. That status confirms a signing secret is configured; verify a settled payment separately below.
Associate a Checkout Session with your order
Setmetadata.order_id to your application’s non-personal order ID when creating the Session on your server. Use the same order ID as your browser purchase if both sources report that order. Plainrouter trims this value and uses it as the purchase’s order ID; without it, the Checkout Session ID is the fallback. client_reference_id does not select Plainrouter’s order ID. Keep order IDs within 128 characters so they remain readable.
This request calls Stripe directly, not the Plainrouter SDK. It creates a hosted, one-time Checkout Session. Set the variables from trusted server configuration and an existing order: select the Stripe Price on your server and persist one idempotency key for that checkout attempt. Reuse the key only when retrying the same request. Keep the Stripe secret key server-side.
id alongside your order and redirect the customer to its url. See Stripe’s Create a Checkout Session reference for the complete request. Session creation and the success-page redirect are not payment verification.
How do I verify a settled purchase?
Inspect the endpoint’s delivery in Stripe for a known Checkout Session, then compare the order and amount with Signals. Plainrouter records revenue only when the settlement event haspayment_status exactly paid.
An illustrative paid-webhook response is:
event_id to find the retained record through GET event. For this revenue flow, the check is the purchase record and value, not a Meta accepted delivery. A 404 can mean the wrong workspace or that the record is outside read retention.
Duplicate paid notifications for the same Session reuse one purchase record and return duplicate: true. The first recorded purchase uses its settlement-confirming webhook’s time. An older duplicate can return duplicate: true without an event_id; do not create another purchase to force a receipt or recover expired payload data. Supported refund notifications record an idempotent negative revenue adjustment. Plainrouter first tries the saved PaymentIntent-to-purchase link in this workspace, then available order metadata and charge references. Purchase lineage is attached only when a matching purchase exists; an unmatched refund does not establish a browser or advertising attribution link. Do not assume Stripe copies Session metadata onto refund objects; see Stripe metadata propagation.
Why is the Stripe purchase missing?
Stripe webhook redelivery and Plainrouter destination replay are different operations. Destination replay cannot create a missing Stripe revenue record.
Reliability behavior
- Event requests use browser
keepalivewhere available. - A granted event still in flight during
pagehideis also sent as a text beacon. - Conversion-event requests can receive at most one retry after network failures or retryable responses, within the request’s retry budget. Arrival requests are never retried.
- Landing click IDs stay in document memory and are transmitted only with a complete consent grant; the legacy
pr_click_idscookie is no longer used for storage. - First-party browser identifiers can still have shorter lifetimes when a browser applies its own storage restrictions.
Avoid duplicate Meta events
After you connect Meta, continue to track throughsignalq. The managed browser Pixel and Plainrouter server delivery share one event ID.
Choose one purchase owner before adding a confirmation-page event:
Send one managed purchase after payment confirmation
Use this recipe when the browser owns the purchase. Your application must first confirm payment on its server and supply the paid order’s non-personal string ID, decimal total, currency, and item count. A success URL alone is not proof of payment. Run this code after the Signals script loads and your CMP has reported the full grant. The map shares one attempt between repeated callbacks for the same order in the current document:track in an automatic retry loop: an uncertain response can follow an accepted request, and a new call creates a new ID.
The managed flow sends the browser Purchase with Pixel eventID and submits the matching server Purchase with event_id. You do not need an additional fbq or backend purchase call. Passing the returned ID to another API request does not enrich the already-accepted event.
Check the purchase pair
- Confirm the order was paid and the CMP reported all three grants before tracking.
- Inspect the conversion request’s
event_id,event_name, and value data. Internal retries should retain the same ID. - Check the event’s destination delivery trace, then compare the browser and server names and IDs in the intended Meta dataset. Meta’s deduplication guide explains the matching fields.
- If duplicates remain, inventory confirmation-page handlers, tag-manager tags, theme Pixels, commerce plugins, Stripe webhooks, and backend jobs. Remove overlapping senders after validating the intended flow.
Check a payload before sending it
Use the local checks alongside the relevant troubleshooting guide:- Missing or duplicate Meta events: inspect a synthetic native Meta payload. This checker expects Meta’s
dataenvelope, not a PlainrouterPOST /eventsbody. - Event-ID deduplication: compare browser/server event names and IDs.
- Browser identifiers: inspect
_fbpand_fbcformatting.