fbc / fbp / IP / user-agent capture pipeline

How Smolhub's Meta Conversions API (CAPI) sends now include real browser signals for website bookings — raising Event Match Quality without ever sending fabricated data for bookings that never touched a website.

Visitor's browser
CF Worker (smolsites-ops)
Smolhub
PracticeHub
Meta Graph API
No website visit (e.g. WhatsApp)
flowchart LR
  subgraph L1["Real-time — every website booking visit"]
    A["Visitor's browser<br/>Lands on booking thank-you page<br/>real _fbp / _fbc cookies already set"]
    B["CF Worker<br/>smolsites-ops<br/>reads cookies, cf-connecting-ip, user-agent"]
    C["Smolhub<br/>POST /api/meta-events/capi-signal<br/>stores keyed by client, pat, atid"]
    A -->|thank-you request| B
    B -->|POST capture| C
  end

  subgraph L2["Every ~30 minutes — CAPI cron, all clients"]
    D["PracticeHub<br/>Booking created<br/>any channel: web, WhatsApp, phone"]
    E["Smolhub — CAPI cron<br/>builds Schedule / FirstBookingComplete<br/>looks up capture by pat, atid, ≤ 7 days"]
    D --> E
  end

  G["No matching capture<br/>e.g. WhatsApp / phone booking"]
  G -.->|no capture, sent without those fields| E
  C -->|found, attach fbc/fbp/IP/UA| E

  F["Meta / Graph API<br/>receives pixel + CAPI events<br/>same event_id on both channels"]
  E -->|CAPI event| F
  A -.->|Meta pixel fbq: same real signals, already automatic| F

  classDef browser fill:#2a78d6,color:#fff,stroke:#1c5cab
  classDef worker fill:#1baf7a,color:#fff,stroke:#128a5c
  classDef hub fill:#4a3aa7,color:#fff,stroke:#352878
  classDef ph fill:#eb6834,color:#fff,stroke:#b94e22
  classDef meta fill:#e34948,color:#fff,stroke:#a92e2d
  classDef nocap fill:#898781,color:#fff,stroke:#6b6a64,stroke-dasharray: 4 3

  class A browser
  class B worker
  class C,E hub
  class D ph
  class F meta
  class G nocap
    

No fabrication rule: the only fields that ever reach Meta are ones a real request produced. A booking with no capture row (the muted path) sends without fbc/fbp/client_ip_address/client_user_agent — exactly what happens today. Nothing is invented to raise the score.
Join key: patient.hashed_id + appointment_type_id — the same pair already used to build the shared event_id that lets Meta dedupe the pixel and CAPI deliveries for one booking.