PRSVR
[04] The Lab · Systems
PRSVR.store  /  drop in, 23 August 2026

Three files that turn the lights on.

Your storefront can't see what buyers do, and the links you send in DMs come out bare. These fix both, they're written against the API your site already serves, and nothing a customer sees changes.

og_prerender.py
Real link previews. A product link in a DM shows the photo, the name and the price.
pixel.js
Meta Pixel that survives a React SPA, and pairs every browser event with a server event.
capi.py
Conversions API. The half that iOS and ad blockers can't strip out.
Why this order

The previews pay off today, the pixel pays off in October

Link previews are a same day win because you're already sending product links and they're already landing flat. The pixel is slower and worth more, because an audience has to accumulate before any campaign can work, so the sooner it starts collecting the sooner spend becomes worth doing. Both are one afternoon.

One thing to get right

The browser and the server both report the same action, and Meta only collapses them into one event when the event_id matches on both sides. That shared id is the entire trick. If your numbers ever look doubled, this is what broke.

Install

Six steps

1
Create the pixel and the token
Events Manager, create a dataset, copy the pixel ID. Then Settings, Generate access token. Both go in the backend environment. Keep the test code set while you verify, then take it out so real numbers land in real reporting.
.env
META_PIXEL_ID=1234567890
META_CAPI_TOKEN=EAAG...
META_TEST_EVENT_CODE=TEST12345   # remove for production
2
Base snippet
Into public/index.html, in the head. Note there's no PageView call in it on purpose, step 3 handles that, and leaving it here as well counts your first page twice.
public/index.html
<script>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init','YOUR_PIXEL_ID');
</script>
3
Wire the SPA
Save pixel.js to src/lib/, then mount the page view hook inside your Router and call the trackers from the components that already have the product in hand.
src/App.jsx
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { usePixelPageViews, trackViewContent, trackAddToCart,
         trackInitiateCheckout, trackDeposit } from './lib/pixel';

function Analytics() {
  usePixelPageViews(useLocation, useEffect);
  return null;
}
// <BrowserRouter> ... <Analytics /> ... </BrowserRouter>

// product page:      useEffect(() => { if (product) trackViewContent(product); }, [product]);
// add to cart:       trackAddToCart(product, qty)
// checkout button:   trackInitiateCheckout(items, total)
// "Make A Deposit":  trackDeposit(product, product.price * 0.5)
Made to order

trackDeposit reports the full piece value, not the 50% deposit. That's deliberate. Optimising toward half the basket teaches Meta to go find you the customer who spends half as much.

4
Mount the server side
Middleware before the static handler, router under /api. pip install httpx and that's the whole dependency.
main.py
from fastapi import FastAPI
from capi import router as meta_router
from og_prerender import OGPrerenderMiddleware

app = FastAPI()
app.add_middleware(OGPrerenderMiddleware)   # before the static handler
app.include_router(meta_router, prefix="/api")
5
Purchase, the one that pays for the rest
The browser can't be trusted to report a sale because Shopify owns the thank you page. Fire it from the server when the order confirms. Best path is a Shopify orders/create webhook, so it lands whether the sale came from the site, the Instagram shop, or a draft order you sent by hand.
orders webhook
from capi import MetaEvent, send_event

await send_event(request, MetaEvent(
    event_name="Purchase",
    event_id=f"order-{order['id']}",      # stable and unique per order
    value=order["total"],
    currency="USD",
    order_id=str(order["id"]),
    content_ids=[str(i["variant_id"]) for i in order["items"]],
    contents=[{"id": str(i["variant_id"]), "quantity": i["qty"], "item_price": i["price"]}
              for i in order["items"]],
    email=order["email"],
    phone=order.get("phone"),
))
6
Ship a fallback card
Any page without its own image falls back to /og-default.jpg. Make one at 1200x630 and drop it in public/.
The files

Full source

Drop them in as they are. Every value that needs changing is an environment variable, so there's nothing to hunt for in the code itself.

og_prerender.py
pixel.js → src/lib/pixel.js
capi.py
Verify

In this order, or you'll chase ghosts

1
Meta Pixel Helper on a product page
One PageView, one ViewContent, and no duplicates when you navigate.
2
Events Manager, Test Events
Browser and server rows for the same action collapse into one, and the source reads "Browser and Server". Two separate rows means the event_id isn't matching, which is the only thing that ever goes wrong here.
3
Sharing Debugger
developers.facebook.com/tools/debug, paste a product URL, and you should get the photo, the name and the price.
4
Send yourself the link
iMessage and an Instagram DM. That's the real test, and it's the one that changes how the business feels day to day.
5
Event Match Quality, a few days later
Under 6 means more identifiers need to go into user_data. Email and phone on the purchase event move it the most.
What it turns on

Once purchases are landing, and not before

None of it exists until the events do. That's the whole reason this goes first.

PRSVR

Strengthen the athlete. Sharpen the scholar. Remove every excuse.

[01]Archive[02]Sports [03]Academy[04]Lab
Written against PRSVR.store as served, 23 August 2026
Todd Walton · Project Baseline