Event contract
- 01install: after Shopify HMAC and OAuth state/nonce validation
- 02trial_started: after your billing system records the trial
- 03trial_converted: when that trial becomes paid
- 04paid: after a verified payment or paid subscription start; include positive amountCents and currency when known
- 05qualified: only after your own documented server rule passes
- 06Reuse the same eventId for retries
- 07Use the same acquisitionId captured before installation
- 08Use the stable myshopify.com domain or internal merchant ID as customerRef
Next.js or Remix server
await fetch('https://app.withmarlo.app/api/tracking/server-events', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MARLO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
teamSlug: 'acme',
publicKey: process.env.MARLO_PUBLIC_KEY,
eventId: billingEvent.id,
acquisitionId: session.marloAcquisitionId,
name: 'paid',
customerRef: session.shop,
amountCents: 4900,
currency: 'USD',
occurredAt: billingEvent.createdAt,
}),
})Authenticate the calling route and load the acquisition ID from your own signed session or database. Never accept a browser-supplied merchant identity as payment evidence.
Expected response
A first request returns {"accepted":true,"duplicate":false}. Repeating the same source event ID returns {"accepted":true,"duplicate":true}. A mapped Google Ads row is then queued, validated or recorded as unattributed.
Verification
- 01Open Tracking and confirm the event is marked Server
- 02Confirm customerRef and event time match the source record
- 03Open Google Ads and find the same event type
- 04Confirm click evidence is present or investigate the first-party handoff
- 05In validation-only mode expect validated rather than an applied Google conversion
- 06Do not change the event ID merely to bypass a duplicate
Failure handling
- 01400: validate required strings, ISO timestamp, supported event name and amount/currency pair
- 02401: verify team slug, private key and tracking source ownership
- 03Duplicate: treat as successful idempotent delivery
- 04Unattributed: inspect click capture and acquisition handoff; do not fabricate an ID
- 05Rejected: use the displayed Google reason before retrying
- 06Timestamp: send the real event time and deliver within seven days to Marlo
Security and limits
- 01Keep marlo_sk_ in a secret manager and rotate it from MCP if exposed
- 02Do not log the private key or put it in a browser bundle
- 03Do not weaken Shopify OAuth CSRF checks
- 04Do not send merchant email or unrelated customer data to create an ad match
- 05Marlo sends only eligible mapped events that have a supported captured click ID
- 06Mapping creation is not retroactive
Return to the complete Google Ads connection guide for account setup, validation, diagnostics and disconnection.