Tracking & Attribution

Why Isn't Meta's CAPI Purchase Firing for Instagram In-App Buyers on iPhone?

Instagram ad buyers on iPhone pay in another app, return in Safari and Meta gets no CAPI Purchase. Why the click is lost and how to send it from the order.

Quick answer

Because the purchase finishes in a different browser. An Instagram ad opens your store inside Instagram's in-app browser, which keeps its own cookies. If the buyer pays in an external app and returns in Safari, the thank-you page loads without the ad click's _fbc cookie, or never loads. Fix it by saving the click on the order at checkout and sending Purchase server-side from the paid order.

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Because the purchase finishes in a different browser from the one that saw your ad. On iPhone, an Instagram ad opens your store inside Instagram’s in-app browser, which keeps its own cookies. When the buyer pays in an external app and returns in Safari, the thank-you page loads without the ad click’s _fbc cookie, or never loads at all. A Purchase tied to that page goes missing or arrives without the click.

The complaint is always the same shape: “Buyers who click my ad inside Instagram on iPhone, pay in an external app and come back in Safari never get a CAPI Purchase, so Meta can’t credit the ad.” Desktop orders look fine. The iPhone Instagram slice is where Purchases thin out.

Here’s what happens between the two browsers, how to confirm it on your own store, and how to send the Purchase from the order so the browser switch stops mattering.

What is the Instagram in-app browser, and why does it matter?

The in-app browser is the web view Instagram opens when someone taps a link or an ad. It’s not Safari. It runs inside the Instagram app with its own cookie jar and local storage, so anything your store or the Meta Pixel saves there stays there. Safari on the same iPhone can’t read it.

When the shopper taps your ad, the landing URL carries an fbclid click identifier. If the Meta Pixel loads on that page, it stores the click in a first-party cookie called _fbc, and it sets a browser id cookie called _fbp. If you’re fuzzy on what each id does, gclid vs fbclid vs UTM parameters covers the difference.

Both cookies live in Instagram’s web view. Meta’s documentation describes fbc as the parameter that ties a server event back to the ad click. As long as the shopper browses, adds to cart and checks out inside Instagram, every event can carry it.

Why does the purchase lose the ad click when the buyer comes back in Safari?

Because the payment step hands the shopper to another app, and the return trip opens in the phone’s default browser, usually Safari. The thank-you page then loads in a browser that has never seen your ad. It has no _fbc, no _fbp, and none of the session data your plugin saved in Instagram.

Walk through one order:

  1. 9:02 — Ava taps your Instagram ad. Your product page opens in Instagram’s web view with ?fbclid=IwAR… in the URL. The pixel sets _fbc and _fbp in that web view.
  2. 9:05 — Ava adds to cart and fills in checkout: email, phone, address. The order is created in WooCommerce as pending.
  3. 9:06 — Ava picks a payment method that opens an external app: PayPal, a bank’s 3-D Secure approval or a pay-later provider.
  4. 9:07 — Ava approves. The provider sends the shopper back to your order-received URL, and iOS opens it in Safari.
  5. 9:07 — Safari loads the thank-you page with a fresh cookie jar. No fbclid, no _fbc.

Two browsers on one iPhone: the Instagram in-app web view on the left holds the fbclid, _fbc and _fbp from the ad click and the checkout with email and phone; the payment app sits in the middle; Safari on the right loads the order-received page with empty cookies, so the browser Purchase arrives with no click id, while an order-webhook path below sends a server Purchase carrying the click saved at checkout

Why does the CAPI Purchase disappear rather than just lose attribution?

Because many WooCommerce Meta setups send their server-side Purchase from the same thank-you page request that prints the browser pixel. “Server-side” there still means “triggered by a page load”. If Ava closes the payment app and never comes back, no page loads and nothing is sent, browser or server. If Ava does come back in Safari, the event fires but carries Safari’s empty cookies.

So one cause gives two failures. Some Purchases never fire. Others fire with no click identifier linking them to the ad. This is a close cousin of the gateway problem in what share of WooCommerce buyers never reach the thank-you page, with an iPhone-specific twist: even the buyers who do reach it arrive in the wrong browser.

How do you confirm this is what’s happening on your store?

Place a real test order from an iPhone through an Instagram link, pay with a method that opens another app, and watch where you land. Then compare a week of paid WooCommerce orders against Purchases received in Events Manager, and check how many of those Purchases carry a click id. The gap shows up as missing events and missing fbc.

  1. Reproduce it. Post the product link in an Instagram story or DM to yourself, open it on an iPhone, and pay with PayPal or a card that triggers your bank app. Note which browser shows the thank-you page.
  2. Check Test Events. With Events Manager’s test view open, see whether a Purchase arrives, and whether its parameters include fbc. The same steps you’d use to check your Meta CAPI is working apply here.
  3. Reconcile a week. Count Processing and Completed orders in WooCommerce → Orders. Compare that with Purchases received in Events Manager, not Ads Manager’s attributed conversions.
  4. Look at click-id coverage. Events Manager’s match-quality details list the customer information parameters your Purchases carry and how often. Low coverage for the click id on Purchase while PageView and AddToCart look healthy points at the browser switch.

How do you keep the Purchase and the ad click together?

Stop tying the Purchase to the thank-you page. Save the click identifiers on the order while the buyer is still in Instagram’s browser, at checkout. Then send Purchase server-side from the order when it’s paid, reading those saved values. The return browser no longer matters, because nothing depends on it.

Step 1: save _fbc and _fbp on the order at checkout

The checkout request is sent from Instagram’s web view, so its cookies are still there. Copy them onto the order before the shopper leaves for the payment app:

// Classic checkout. Block checkout: also hook
// 'woocommerce_store_api_checkout_order_processed', which receives the order.
add_action( 'woocommerce_checkout_create_order', function ( $order ) {
    foreach ( array( '_fbc', '_fbp' ) as $name ) {
        if ( ! empty( $_COOKIE[ $name ] ) ) {
            $order->update_meta_data(
                $name,
                sanitize_text_field( wp_unslash( $_COOKIE[ $name ] ) )
            );
        }
    }
    // Keep the checkout browser's user agent for client_user_agent.
    $order->update_meta_data( '_capi_ua', wc_get_user_agent() );
} );

If _fbc is missing but you kept the landing URL’s fbclid, Meta’s docs describe how to build fbc from it: fb.1., then a millisecond timestamp, a dot, and the fbclid value. If the pixel never set _fbp, leave it empty. Don’t invent one.

Step 2: send Purchase when the order is paid, not when a page loads

Hook the order’s move to Processing or Completed and send Purchase through the Conversions API with the saved fbc and fbp, the checkout user agent, and hashed email and phone from the order. Use the order number to build the event_id. The full setup is in the guide to tracking WooCommerce purchases server-side to Meta.

Two things to get right. First, if a browser pixel still fires Purchase on the thank-you page, it needs the same event name and event id, or it double counts. Simpler: keep one source for Purchase. Second, ask for a phone number at checkout. Email and phone are the strongest match keys Meta scores, and they’re your fallback when the click id is lost, which is why your Event Match Quality ceiling is set by your checkout form.

How does PartialLeads send the Purchase when an Instagram buyer pays in another app?

PartialLeads takes the WooCommerce Purchase from the order webhook, not from any page, so a buyer who returns in Safari or never returns still produces a Purchase once the order is paid. The tag records fbclid and _fbc when the buyer lands, and the order is matched back to that session by visitor id or by the checkout email or phone.

The Purchase doesn’t wait for a page. The PartialLeads WooCommerce plugin fires nothing on the thank-you page. When an order reaches Processing or Completed, WooCommerce’s webhook delivers it, PartialLeads records it once, and sends Purchase server-side to Meta, TikTok and Pinterest. Pending, on-hold, failed and cancelled orders aren’t sent. The value is the full order total, including tax and shipping after discounts.

The click is captured where the ad landed. On the Instagram web view, the PartialLeads tag stores the landing page, UTMs, fbclid, fbc and fbp on that visitor’s session. When fbclid is in the URL but the _fbc cookie is missing or malformed, PartialLeads rebuilds fbc from it in Meta’s format. It does not create an _fbp that the pixel never set.

The order finds its way back to the click. Each paid order is matched to a session in tiers: visitor id first, then email, phone and IP. The tag captures the email and phone the shopper types into checkout as they type, so the Instagram session already carries them before the shopper leaves for the payment app. When the order webhook arrives with the same billing email, it matches the session that holds the ad click, and the Purchase goes to Meta with that click’s identifiers plus hashed email and phone.

The PartialLeads Leads list and Journey timeline for one buyer: an Instagram ad session in the in-app browser with fbclid and a Partial badge once the email was typed at checkout, a Safari session on the thank-you page, and the paid order matched by email to the Instagram session, next to the Purchases ledger row showing the order total and email match, and the Meta CAPI activity log row with a green delivered Purchase carrying fbc

Where you see it working. The Journey timeline shows the Instagram ad session leading to the order. The Purchases ledger lists every paid order with its value, source and whether it matched a session. The Meta CAPI page lists each server Purchase with its status and time, and the Leads list’s API column shows which conversion APIs that buyer was sent to.

The honest constraints. When the purchase finishes in a different browser, the match relies on email or phone. If the shopper typed one email in Instagram’s browser and pays with a different one, or the tag never saw the checkout fields, the order can land unmatched. It’s still sent to Meta, just without the click, and it can be matched later if that person’s identity turns up. PartialLeads uses its own event ids and doesn’t deduplicate against another plugin’s browser pixel, so switch off any other Purchase source. We control that every paid order is sent; Meta controls how it attributes it. If the order is marked paid days after checkout, Meta’s 7-day event window still applies.

What breaks The mechanism Where you see it in the dashboard
Buyer never returns from the payment app, no Purchase fires Purchase comes from the order webhook once the order is paid; nothing fires on the thank-you page Meta CAPI page, server Purchase per paid order
Thank-you page loads in Safari with no _fbc fbclid and fbc captured on the Instagram session at landing; fbc rebuilt from fbclid when the cookie is missing Journey timeline, Instagram ad session to order
Order isn’t linked to the ad click Order matched by visitor id, then checkout email or phone typed in the Instagram browser Purchases ledger, matched or unmatched per order
Plugin’s thank-you Purchase and server Purchase both count No cross-tool dedup; keep one Purchase source Leads-list API column vs Events Manager sources

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Sources


Frequently asked questions

QWhy do my Instagram ad purchases on iPhone not show up in Meta?
Instagram opens your store in its own in-app browser, which keeps separate cookies from Safari. When a buyer pays in an external app and returns, iOS often opens the thank-you page in Safari, without the ad click cookie. If your Purchase depends on that page, it either fires without the click or doesn't fire at all.
QDoes the Conversions API fix in-app browser tracking on its own?
Not if the server event is triggered by the thank-you page loading. Many setups send the CAPI Purchase from that same page request, so a buyer who doesn't come back sends nothing. The fix is sending Purchase from the paid order itself, with the click identifiers saved on the order at checkout.
QCan Meta still attribute the sale without the fbc cookie?
Sometimes. Meta can try to match the buyer from hashed email, phone and other customer details, but without the click identifier it loses the direct link to the specific ad click. That's why saving fbc at checkout and sending email and phone both matter.
QShould I add a banner asking Instagram users to open my site in Safari?
It rarely helps. Every extra step between ad and checkout loses some shoppers, and you can't make Instagram open ads in Safari. It's more reliable to keep the in-app checkout and fix the data path, so the Purchase is sent from the order whichever browser the buyer ends in.
QIs this only an iPhone problem?
It shows up most on iPhone because payment apps hand the buyer back to Safari, a separate browser from Instagram's. Android in-app browsers can split sessions the same way depending on the payment method. Any setup that ties the Purchase to the thank-you page in the buyer's current browser is exposed.

Find the qualified leads your forms are currently throwing away.

Install PartialLeads on one landing page, send traffic, and compare what your CRM captured against what PartialLeads recovered and qualified.