FloorSync for agents
Setting up as an agent? Read /agent-install.md: the whole setup in plain HTTP calls, dry run first, no installs.
Try it now, no key
An agent can POST any Stripe event to /api/v1/demo/events/stripe with no key (a few calls an hour). It gets back what Meta and GA4 would get, the ledger line, and a short note for its owner with what’s needed before live sends and the sign-up link. Nothing is sent or kept. On MCP, it’s the try_stripe_event tool.
What your agent gets in message_for_owner:
Machine-readable: /llms.txt, /openapi.json, /api/capabilities and the MCP endpoint POST /api/mcp.
1. The owner sets it up
- The floor owner creates an account and gets the floor key (
fs_…). - They save the Meta pixel ID with a Conversions API access token, and the GA4 Measurement ID with a Measurement Protocol API secret.
- They start the plan: $10 a month per floor, 7 days free. Agents can’t buy it; until it starts, live calls answer
402and dry runs still work.
2. The agent forwards each Stripe event
In the floor’s /api/stripe-events route, after handling the event. Every POST sends the floor key and the body’s SHA-256 (the CDN asks for it).
import { createHash } from 'node:crypto'
const text = JSON.stringify(event) // exactly as received
await fetch('https://floorsync.towers.app/api/v1/events/stripe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Floor-Key': FLOORSYNC_KEY,
'x-amz-content-sha256': createHash('sha256').update(text).digest('hex'),
},
body: text,
signal: AbortSignal.timeout(10000),
}).catch(() => {})
Add ?dry_run=1 to get the exact Meta and GA4 payloads back, with GA4’s validation answer, and send nothing.
3. What goes where
| Stripe event | Meta | GA4 |
|---|---|---|
| checkout.session.completed (paid) | Purchase, event_id = session id | purchase, transaction_id = session id |
| checkout.session.completed ($0 trial) | StartTrial | start_trial |
| invoice.paid (subscription_cycle) | Purchase, event_id = invoice id, system_generated | purchase with the stored client_id |
| invoice.payment_failed, refunds, subscription deleted | Ledger only | |
Each event id counts once. Email and phone go to Meta SHA-256 hashed only; GA4 gets no personal data. A failed send is kept in the sync log and retried each hour, 3 tries in all. A floor’s first GA4 send is checked on GA4’s validation server first.
4. Attribution at checkout (recommended)
When the floor creates a Checkout session, post the buyer’s ids so the sale and its renewals match the right visitor:
POST /api/v1/attribution
{"checkout_session_id": "cs_…", "fbp": "fb.1.…", "fbc": "fb.1.…", "client_id": "1234567890.1234567890",
"ip": "203.0.113.7", "user_agent": "Mozilla/5.0 …", "page_url": "https://yourfloor.towers.app/pricing/"}
5. Read the ledger
GET /api/v1/ledger?days=7
{"mode": "live", "days": 7, "currency": "USD", "revenue": 10,
"counts": {"new_paid": 1, "trials": 0, "renewals": 0, "refunds": 0, "failed_payments": 0, "cancellations": 0},
"conversions": {"meta": {"delivered": 1, "failed": 0, "skipped": 0}, "ga4": {"delivered": 1, "failed": 0, "skipped": 0}},
"spend": null, "spend_source": null, "revenue_per_spend": null,
"plan_active": true, "health": [], "note": "Nothing to fix."}
An example answer. Spend comes from Meta Insights when the owner saves an ads_read token, or from POST /api/v1/spend.
Errors
401 no_key/bad_key: ask the owner for the current floor key.402 payment_required: nothing was sent; give the owner theowner_url.400 bad_request: the message says what to fix.