Documentation

Caply docs

Managed Meta Pixel + Conversions API. Paste https://trycaply.com/v1.js. Send plaintext identifiers; Caply hashes them once before Meta. Pixel and CAPI share the same event_id.

On this page

Quick start

  1. Create an account and a workspace at /auth/signup.
  2. In the dashboard, connect a Meta Pixel ID and a Conversions API token from Events Manager → Settings → Conversions API (server / Set up manually) → Generate access token. Caply encrypts the token at rest. Never paste that token into a theme, custom pixel, or GTM.
  3. Create a source. Copy the API key shown once.
  4. Paste https://trycaply.com/v1.js. If you use Next.js, load it with next/script. Then fire Purchase with email and value.
  5. Send a Purchase or Lead with email (or phone) and a stable eventId. Check Dashboard → Events, then Events Manager (look for a Server source).

Pro ($29/mo) and Team ($99/mo) start a 14-day Stripe trial that requires a card. There is no free Caply workspace. Cancel in the customer portal before the trial ends to avoid the first invoice.

Browser Pixel vs Caply CAPI

These are two pipes. Mixing them up is how people paste a CAPI token into theme code and think the job is done.

  • Browser Pixel fbq in the page. Sets _fbp / _fbc. Dies with blockers and ITP.
  • Caply CAPI — our servers POST to Meta Graph using the token stored in Dashboard → Pixel. Ingest is /api/v1/track from the snippet. That token never belongs in theme code.

A second CAPI sender on the same Pixel without a shared event_id double-counts.

Snippet

No-code sites load https://trycaply.com/v1.js. Replace the placeholders with your Pixel ID and Caply source API key from the dashboard (not a Meta access token).

HTML — head
<script>
  !function(c,o,n,v,e){c.Caply=e,c[e]=c[e]||function(){(c[e].q=c[e].q||[]).push(arguments)};var s=o.createElement("script");s.async=true;s.src="https://trycaply.com/v1.js";var t=o.getElementsByTagName("script")[0];t.parentNode.insertBefore(s,t)}(window,document,"script",0,"cv");
  cv("init", "YOUR_PIXEL_ID", "YOUR_API_KEY");
  cv("track", "PageView");
</script>

Next.js can load the same script with next/script:

Next.js — next/script
"use client";
import Script from "next/script";

export function CaplySnippet() {
  return (
    <Script
      src="https://trycaply.com/v1.js"
      strategy="afterInteractive"
      onLoad={() => {
        window.cv("init", process.env.NEXT_PUBLIC_META_PIXEL_ID, process.env.NEXT_PUBLIC_CAPLY_API_KEY);
        window.cv("track", "PageView");
      }}
    />
  );
}

The default snippet fires PageView only. Money events need a second call with value and identifiers:

JavaScript
cv("track", "Purchase", {
  value: 89.0,
  currency: "USD",
  content_ids: ["sku-1"],
});

The snippet reads _fbp / _fbc and the user agent. Pass email and phone on Purchase/Lead from your checkout script.

SDK @caply/track (in progress)

Launch path is the snippet https://trycaply.com/v1.js. The npm package @caply/track exists in this repo (0.1.2) for Next.js, but it is in progress — not a launch requirement, and not published as a supported install path yet. Prefer next/script with v1.js until the package is published.

Terminal
npm install @caply/track
TypeScript — init
import { createCaply } from "@caply/track";

export const caply = createCaply({
  pixelId: process.env.NEXT_PUBLIC_META_PIXEL_ID,
  apiKey: process.env.NEXT_PUBLIC_CAPLY_API_KEY,
  apiOrigin: "https://trycaply.com",
  autoCAPI: true,
  autoDedup: true,
});
  • apiOrigin defaults to https://trycaply.com if omitted.
  • autoCAPI (default true) POSTs to /api/v1/track.
  • autoDedup (default true) generates eventId when you do not pass one. Pixel eventID and CAPI use the same value.

Send plaintext PII

Send raw email and phone. Caply SHA-256 hashes identifiers on the server before Meta. Do not pre-hash unless the value is already a SHA-256 hex digest — those are not hashed again.

TypeScript — Purchase
await caply.trackPurchase({
  eventId: crypto.randomUUID(),
  email,
  value: amount,
  currency: "USD",
});
TypeScript — other events
caply.track("PageView");
await caply.trackLead({ email, eventId });
await caply.trackAddToCart({ contentIds: ["sku-1"], value: 49, currency: "USD" });
await caply.trackCustom("Subscribe", { email, value: 9, currency: "USD" });

Helpers: track, trackPurchase, trackLead, trackAddToCart, trackCustom. Optional user fields include phone, firstName, externalId, fbp / fbc (read from cookies in the browser if omitted).

Pixel + CAPI token

Caply needs two Meta credentials for the CAPI loop, and a third if you use Ghost conversions.

  1. Pixel ID — copy it from Events Manager → dataset Settings (eventsmanager.facebook.com/events_manager2/list/dataset/{pixelId}/settings).
  2. Conversions API token — same Settings → Conversions API (server / Set up manually) → Generate access token. Paste it in Dashboard → Pixel. Caply encrypts it (CAPITOKEN_ENCRYPTION_KEY) and uses it on flush. Not an ads_read token.
  3. Optional Test event code when you click Test Pixel. Confirm the event in Events Manager Test events.

After connect, the dashboard can show Meta Event Match Quality (Pro/Team) from Pixel diagnostics. That is Meta's delayed EMQ, cached about an hour — not Match Forecast.

Events, event_id, and dedup

Every ingest is POST /api/v1/track with the source API key. A Pixel + CAPI pair that shares event_id is still one billed ingest. Failed retries are not counted twice.

Use the same id on both sides. The SDK accepts eventId or event_id. The browser Pixel call uses Meta's eventID option. Without a shared id, Meta double-counts.

eventTime must be within the last 7 days and not in the future (Meta window). Status starts as queued. A worker flushes up to 500 events every 5 minutes to Graph API v22, with retries (cap 5, backoff up to 1 hour).

Duplicate dedup_key (pixel + event id + time) returns status: "deduplicated" and does not insert a second row.

Match Forecast

Every ingest returns a 0–10 identifier score from hashed email, phone, _fbp, _fbc, external_id, and weaker geo / IP / UA signals. Paid plans see Forecast on their own events.

Money events (Purchase, Subscribe, StartTrial, Lead) under 5 / 10 are blind. Their value rolls into Recoverable Match Gap on Overview. That number is a planning estimate of poorly identified checkouts — not a promise Meta will credit the revenue, and not campaign ROAS.

Not Meta EMQ. EMQ is Meta's delayed, averaged match quality in Events Manager (Pro scorecard). Forecast is Caply's payload score at ingest.

Ads Coach board

Dashboard → Ads Coach. GET/POST /api/v1/assistant. The board scores delivery, hashed email, _fbp, and Purchase value from the last 250 events in the workspace.

Drag the canvas. Click a funnel event (PageView, ViewContent, AddToCart, InitiateCheckout, Purchase) to expand volume, delivery, email, _fbp, and — on money events — value and Forecast. Click a stat card to open the side panel and ask the coach.

The rule engine always runs. An optional language-model rewrite of aggregates only (not raw PII) needs REPLICATE_API_TOKEN (Llama 3 8B). Screenshot vision uses LLaVA when configured. OpenAI is a fallback. Ads Coach is not a creative generator.

Scanner

Public scanner: /scanner. It reads static HTML for Pixel IDs, GTM, GA4, Purchase, event_id, and consent hints. Public scans are not stored.

Signed-in operators use Dashboard → Scanner for the workspace URL, evidence, and history. Deep scan (Pro/Team) executes JavaScript via Browserless and lists facebook.com/tr network hits. Deep scan is Pro 100 / Team 500 per month. Plan is taken from the signed-in workspace, not from the request body.

Tracking radar

Dashboard → Radar stores competitor (or prospect) URLs and compares public HTML — Pixel, GTM, Stape fingerprints, event_id — to your workspace. It is not their ad account.

Limits: Pro 15, Team 50 URLs. Unpaid workspaces have no dashboard. SPAs that inject tags after first paint need a deep scan on your site, not radar.

Ghost conversions

Dashboard → Ghost conversions. GET /api/v1/ghost-conversions. Pro and Team only.

Caply counts sent Purchase (and optionally Lead) events, then joins Ads Manager Insights action counts by calendar day — not by event_id. Ghost = Caply-sent minus Ads Manager. Untracked = Ads Manager minus Caply. Attribution windows, Pixel-only purchases, and midnight timezone edges all move the counts. This is not a claim Meta dropped those events, and not a campaign ROAS table.

Requires a separate Meta system user with ads_read plus an ad account id. That token is not the Pixel CAPI token. Insights responses are cached (about 30 minutes) with a manual refresh. Do not poll hourly.

Environment and ops

Production runs on Vercel with Root Directory web. Copy names from web/.env.example. Never paste live secrets, sk_live, service role keys, or Meta tokens into chat or docs tickets.

  • CRON_SECRET must equal INTERNAL_FLUSH_SECRET. Vercel Hobby only allows a daily Cron (UTC): /api/internal/flush-cron at 08:00 UTC. The 5-minute flush is GitHub Actions .github/workflows/flush.yml (secrets CRON_SECRET and optional SITE_URL, default https://trycaply.com) or an external ping. Vercel Pro can restore */5 in vercel.json.
  • Stripe webhook: https://trycaply.com/api/webhooks/stripe. Enable Customer Portal. Health: /api/health.
  • Optional: BROWSERLESS_TOKEN (deep scan), REPLICATE_API_TOKEN (Ads Coach LLM + screenshot vision). Without them, static scan and the rules engine still work.
  • Schema: run Supabase SQL migrations in order (001 through 007). Ghost conversions needs 006. Scanner detected history needs 007.

Google sign-in stays on trycaply.com. Create a Google Cloud Web OAuth client. JavaScript origins: http://localhost:3000, https://trycaply.com. Redirect URIs: http://localhost:3000/auth/google/callback and https://trycaply.com/auth/google/callback. Do not add *.supabase.co. Server env: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET. In Supabase, enable Google and paste the same client ID (needed for ID token sign-in). Email confirmation still uses /auth/callback.

We use essential cookies for authentication and session management. By using Caply you agree to our Cookie Policy, Privacy Policy, and Terms.