Conversion signals
Conversion APIs match a server-side event back to the person who saw your ad. The more identity you supply, the higher the match rate. Your app hands Ranksy those match keys once per merchant session, and Ranksy forwards them to Meta's Conversions API — with more conversion APIs to follow.
/capi is ingestion — a write — so it never counts against your plan's request quota and keeps working after your monthly read quota is spent.
This endpoint enriches an existing installation. Call /identify for a merchant first. If Ranksy has never seen the domain for this app, /capi returns 404 shop_not_identified instead of creating a record.
Send conversion signals
Store the latest identity and session match keys for one merchant. Send raw values — Ranksy hashes them per platform at send time. Call it once per merchant session; every page load is fine, because newer signals replace older ones and blank fields never overwrite stored ones.
Requires the capi:write scope. That scope is separate from crm:write on purpose: forwarding merchant PII to ad platforms is a different grant from filling your CRM, so you can revoke one without the other.
Path parameter
- Name
app- Type
- string
- Description
The team app's ULID, e.g.
01JN4HKQX0000000000000000.
Request body
- Name
myshopify_domain- Type
- string
- Description
The merchant's
.myshopify.comdomain.
- Name
occurred_at- Type
- string
- Description
ISO-8601 time these signals were observed. Defaults to now. Ranksy uses it to reject out-of-order payloads — see Freshness.
- Name
user- Type
- object
- Description
Durable identity:
email,phone,first_name,last_name,zip,city,region,country. All optional.
- Name
context- Type
- object
- Description
Session state:
ipanduser_agent, as observed on the merchant's own request to your backend. All optional.
Request
curl -X POST https://ranksyapp.com/api/v1/apps/01JN4HKQX0000000000000000/capi \
-H "Authorization: Bearer rk_live_..." \
-H "Content-Type: application/json" \
-d '{
"myshopify_domain": "acme-supplies.myshopify.com",
"occurred_at": "2026-07-23T14:36:00Z",
"user": {
"email": "[email protected]",
"phone": "+14155551234",
"first_name": "Sarah",
"last_name": "Chen",
"zip": "94107",
"city": "San Francisco",
"region": "California",
"country": "United States"
},
"context": {
"ip": "203.0.113.42",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ..."
}
}'
200 — Success
{
"accepted": true,
"shop_id": 68134502401,
"stored": ["email", "phone", "zip", "ip", "user_agent"]
}
stored lists the fields this call actually wrote, using your own key names. A payload ignored as stale returns "accepted": true with an empty stored.
Send raw values
Do not pre-hash anything. Each conversion API normalizes differently before hashing. Meta, for example, strips non-ASCII bytes from city names rather than transliterating them, so São Paulo hashes as sopaulo. Ranksy applies the correct normalization per platform at send time. A pre-hashed value cannot be normalized, so it will not match.
Freshness
Ranksy keeps the latest known signals per merchant, under two rules:
- Newest wins. A payload whose
occurred_atis older than the stored value is ignored, so an out-of-order request cannot regress a fresher IP. - Blank never overwrites. An omitted or empty field leaves the stored value untouched. This makes the endpoint safe to call on every page load: send only what you have, and a session where you cannot resolve the merchant's email does not erase the email you sent yesterday.
Why there are no click ID fields
There is no fbclid, fbp, or rdt_cid parameter, by design. A merchant who clicks your ad reaches the App Store listing, installs through Shopify's OAuth flow, and lands in an embedded admin iframe where third-party cookies are partitioned. The browser context that held the click ID is gone by the time the conversion happens. The app cannot read it, so asking for it would only invite bad data.
Matching runs instead on external_id — the shop's domain and numeric Shopify ID, which collides with the identifier Shopify's own App Store pixel emits — plus the hashed identity you send here.
Errors
Beyond the universal 401 / 403 / 422 / 429 (see Errors):
- Name
404 — shop_not_identified- Type
- invalid_request_error
- Description
No installation exists for this domain on this app. Call
/identifyfirst.
- Name
422 — validation_failed- Type
- invalid_request_error
- Description
Most often a malformed
emailorip.
Rate limits
Standard rate limits apply: 120 requests/min, 50,000 requests/day per key.
Your data responsibility
You are sending your merchants' personal data to third-party advertising platforms. The legal basis for that — consent, disclosure, and a data processing agreement where required — is your obligation to your merchants, not Ranksy's.