Context
The DecoCMS Concierge (top-of-funnel WhatsApp bot, repo decocms/concierge) hands every prospect a tracked signup link in its CTA:
https://studio.decocms.com/signup?src=wa&ref=<leadId>
src=wa — channel (WhatsApp).
ref=<leadId> — an opaque per-conversation id (e.g. wa8hcxfm). It maps back to a specific WhatsApp lead in the Concierge's lead store.
The Concierge already does its half: it emits the link, stores the ref on the lead, and exposes an endpoint to mark a lead converted. What's missing is the Studio side: Studio needs to (1) preserve ref through the signup flow and (2) tell the Concierge when that signup actually completes, so we can close the WhatsApp → signup loop and report true conversion.
Goal
When a user who arrived via …/signup?src=wa&ref=X finishes creating their account, Studio makes one server-side call back to the Concierge with ref=X. That flips the lead to signed_up and lights up conversion in the funnel report.
What Studio needs to do
- Capture
ref (and src) on landing. On /signup (and any entry route that can carry these params), read ref/src and persist them so they survive the entire funnel — page navigations, email verification, and especially OAuth round-trips (Google/GitHub) where query params are commonly lost. Cookie (e.g. httpOnly not required, but first-party) or server-side session both work; just don't lose it across redirects.
- Fire on successful signup. At the point a new account is created (server-side hook / post-signup callback — not on every login), if a
ref is present, POST it to the Concierge (details below). Server-side, exactly once.
- No-op when absent. If there's no
ref, do nothing (most signups won't have one).
- (Nice to have) Persist
src/ref on the user/org record too, so Studio's own analytics can attribute signups by channel independently of the Concierge.
API contract (Concierge endpoint)
POST https://decocms-concierge.deco-ceo.workers.dev/event
Authorization: Bearer <CONCIERGE_MCP_AUTH_TOKEN>
Content-Type: application/json
{ "type": "signup", "ref": "wa8hcxfm" }
- Auth:
Authorization: Bearer <token> (also accepts x-mcp-auth: <token> or ?token=). The token is the Concierge's MCP_AUTH_TOKEN — store it as a Studio server secret; never expose it client-side. We'll share the value via the secrets manager (do not paste it in this issue/PR).
- Response:
200 { "ok": true, "lead": { "phone", "ref", "signedUp": true, "signedUpAt" } } on success; 404 { "ok": false } if no lead matches the ref (safe to ignore/log).
- Idempotent: calling twice is fine (it just re-sets
signedUp).
- You may send
{ "phone": "<e164>" } instead of ref if Studio happens to know the WhatsApp number, but ref is the expected path.
Equivalent option: call the Concierge MCP tool record_conversion({ ref }) if Studio already speaks MCP to it. The /event POST is the simplest path.
Example:
curl -X POST https://decocms-concierge.deco-ceo.workers.dev/event \
-H "Authorization: Bearer $CONCIERGE_MCP_AUTH_TOKEN" \
-H "content-type: application/json" \
-d '{"type":"signup","ref":"wa8hcxfm"}'
Acceptance criteria
Security notes
- The callback must be server-to-server. Do not embed
CONCIERGE_MCP_AUTH_TOKEN in client JS.
ref is opaque and non-sensitive (no PII); fine to keep in a first-party cookie/query.
Open questions for the Studio team
- Where is the canonical "account created" server hook in Studio's auth (Better Auth) flow to attach this callback?
- Preferred mechanism to carry
ref across OAuth — first-party cookie set on /signup, or encode into the OAuth state?
- Should we also forward
utm_* params for completeness, or just src/ref?
Filed by the Concierge team. Concierge side (link emission, lead store, /event + record_conversion) is already live on main in decocms/concierge.
Context
The DecoCMS Concierge (top-of-funnel WhatsApp bot, repo
decocms/concierge) hands every prospect a tracked signup link in its CTA:src=wa— channel (WhatsApp).ref=<leadId>— an opaque per-conversation id (e.g.wa8hcxfm). It maps back to a specific WhatsApp lead in the Concierge's lead store.The Concierge already does its half: it emits the link, stores the
refon the lead, and exposes an endpoint to mark a lead converted. What's missing is the Studio side: Studio needs to (1) preserverefthrough the signup flow and (2) tell the Concierge when that signup actually completes, so we can close the WhatsApp → signup loop and report true conversion.Goal
When a user who arrived via
…/signup?src=wa&ref=Xfinishes creating their account, Studio makes one server-side call back to the Concierge withref=X. That flips the lead tosigned_upand lights up conversion in the funnel report.What Studio needs to do
ref(andsrc) on landing. On/signup(and any entry route that can carry these params), readref/srcand persist them so they survive the entire funnel — page navigations, email verification, and especially OAuth round-trips (Google/GitHub) where query params are commonly lost. Cookie (e.g.httpOnlynot required, but first-party) or server-side session both work; just don't lose it across redirects.refis present, POST it to the Concierge (details below). Server-side, exactly once.ref, do nothing (most signups won't have one).src/refon the user/org record too, so Studio's own analytics can attribute signups by channel independently of the Concierge.API contract (Concierge endpoint)
Authorization: Bearer <token>(also acceptsx-mcp-auth: <token>or?token=). The token is the Concierge'sMCP_AUTH_TOKEN— store it as a Studio server secret; never expose it client-side. We'll share the value via the secrets manager (do not paste it in this issue/PR).200 { "ok": true, "lead": { "phone", "ref", "signedUp": true, "signedUpAt" } }on success;404 { "ok": false }if no lead matches theref(safe to ignore/log).signedUp).{ "phone": "<e164>" }instead ofrefif Studio happens to know the WhatsApp number, butrefis the expected path.Example:
Acceptance criteria
https://studio.decocms.com/signup?src=wa&ref=ABCand completing signup preservesref=ABCthrough the full flow, including at least one OAuth provider round-trip and/or email verification.POST /eventwith the correctref.refis present, no call is made and signup is unaffected.refresults in the Concierge lead showingsignedUp: true(confirmable via the Conciergeget_lead/get_funnel_reportMCP tools).src/refstored on the new user/org for Studio-side attribution.Security notes
CONCIERGE_MCP_AUTH_TOKENin client JS.refis opaque and non-sensitive (no PII); fine to keep in a first-party cookie/query.Open questions for the Studio team
refacross OAuth — first-party cookie set on/signup, or encode into the OAuthstate?utm_*params for completeness, or justsrc/ref?Filed by the Concierge team. Concierge side (link emission, lead store,
/event+record_conversion) is already live onmainindecocms/concierge.