Developer Documentation
Install tracking, push revenue, and integrate webhooks. Every example below reflects the live API.
Overview and authentication
The Attrevo API is a JSON HTTP API. All application endpoints are served under https://api.attrevo.com/api.
There are three distinct authentication modes:
| Mode | Used by | Credential |
|---|---|---|
| Session (Clerk JWT) | Dashboard requests from the browser | Authorization: Bearer <jwt> |
| API key | Server-to-server, e.g. the revenue API | X-Attrevo-API-Key: atv_live_… |
| Public | Tracking ingestion, consent, snippet | None — rate limited per domain |
A handful of routes sit outside the /api prefix because they are called by browsers or load balancers: /health, /events/track, /events/consent, /snippet/:tenantId/attrevo.js and the payment webhooks.
Tracking snippet installation
One script tag, on every page, immediately before the closing </body> tag. Replace YOUR_TENANT_ID with the value from Settings → Tracking.
<!-- Paste immediately before </body> on every page -->
<script async src="https://api.attrevo.com/snippet/YOUR_TENANT_ID/attrevo.js"></script>The snippet is deliberately small and self-contained. It:
- Assigns a pseudonymous visitor ID in a first-party cookie (
attrevo_vid, 2 years) and a session ID insessionStorage. - Reads
utm_source,utm_medium,utm_campaign,utm_contentandutm_term, falling back to the referrer. - Sends via
navigator.sendBeaconwhere available, so navigation is never delayed. - Holds every event until consent is granted, then stores the decision in
attrevo_consent.
Nothing is transmitted before consent. If the visitor declines, no touchpoint is sent at all.
Custom event tracking
To track a call-to-action, add data-attrevo="cta" to any element. The snippet listens on document click and walks up the tree, so it works on dynamically rendered markup with no extra wiring.
<!-- Any element with data-attrevo="cta" reports a cta_click -->
<a href="/pricing" data-attrevo="cta">See pricing</a>
<button data-attrevo="cta" id="hero-signup">Start free trial</button>Each click sends a cta_click event carrying the element's trimmed text (up to 120 characters), its id, and its href when present.
Revenue API reference
POST /api/revenue/record — record a payment from a platform Attrevo does not integrate with directly. Authenticated with an API key, not a session.
Request
| Field | Type | Required | Notes |
|---|---|---|---|
source | string | Yes | Max 32 chars, e.g. selar |
amount | number | Yes | Positive, in major units (naira, not kobo) |
reference | string | Yes | Your unique ID; used to de-duplicate |
customerEmail | string | Conditional | At least one of email or phone is required |
customerPhone | string | Conditional | Normalised to +234 before matching |
currency | string | No | 3-letter code, defaults to NGN |
occurredAt | string | No | ISO 8601; defaults to now |
metadata | object | No | Arbitrary key/value pairs, stored as-is |
curl -X POST https://api.attrevo.com/api/revenue/record \
-H "X-Attrevo-API-Key: atv_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"source": "selar",
"amount": 150000,
"currency": "NGN",
"customerEmail": "ada@example.com",
"customerPhone": "+2348012345678",
"reference": "SELAR-TX-91823",
"occurredAt": "2025-07-26T09:15:00.000Z",
"metadata": { "product": "Attribution Masterclass" }
}'Response
{
"recorded": true,
"revenueEvent": {
"id": "clx8f2k9p0001",
"source": "selar",
"amount": 150000,
"currency": "NGN",
"reference": "SELAR-TX-91823",
"matchedLeadId": "clx7a1b2c0003",
"matchMethod": "email",
"occurredAt": "2025-07-26T09:15:00.000Z"
}
}matchMethod is email, phone or null. A null match still records the revenue — it simply cannot be attributed to a channel yet, and will be re-matched if the lead is identified later.
Webhook integration
Webhooks are the preferred path for supported platforms: revenue arrives in near real time with no polling.
| Platform | Endpoint | Verification |
|---|---|---|
| Paystack | /webhooks/paystack | HMAC SHA512 over the raw body |
| Flutterwave | /api/webhooks/flutterwave | Shared secret header |
| Selar | /api/webhooks/selar | Shared secret header |
| Generic | /api/webhooks/generic | Shared secret + field mapping |
{
"event": "charge.success",
"data": {
"reference": "PSK-8817263",
"amount": 5375000,
"currency": "NGN",
"customer": { "email": "ada@example.com" },
"paid_at": "2025-07-26T09:15:00.000Z"
}
}Signature verification runs against the raw request body before parsing — re-serialising JSON changes the bytes and breaks the HMAC. Deliveries are idempotent on the platform reference, so retries are safe.
The generic endpoint lets you map your own field names onto Attrevo's when configuring the source, for platforms with no native integration.
CSV import
For historical revenue or offline sales, import a CSV under Revenue → Import. Each row is matched using the same email-then-phone logic as the API.
email,phone,amount,currency,reference,occurred_at
ada@example.com,+2348012345678,150000,NGN,INV-001,2025-07-01
chidi@example.com,,89500,NGN,INV-002,2025-07-03
,+2348098765432,240000,NGN,INV-003,2025-07-05emailorphone— at least one per row.amount— major units. Do not include currency symbols or thousands separators.reference— must be unique; duplicates are skipped rather than double-counted.occurred_at—YYYY-MM-DDor full ISO 8601.
Each import is recorded as a batch, so a bad file can be identified and reversed rather than leaving orphaned rows.
API keys
Create keys under Settings → API keys. Keys are prefixed atv_live_ and are shown exactly once at creation — only a hash is stored, so a lost key must be revoked and replaced.
- Send as
X-Attrevo-API-Key, never in a query string. - Each key is scoped to one tenant and carries no user identity.
- Revoking takes effect immediately on the next request.
- Keys belong on a server. Anything in browser JavaScript is public — use the snippet there instead.
Rate limits
Tracking ingestion is rate limited per domain in Redis to protect the platform from misconfigured or malicious pages. Authenticated endpoints are additionally bounded by your plan's monthly event allowance.
Exceeding a limit returns 429. Two distinct cases share that status, distinguished by the body:
- Transport rate limit — retry with exponential backoff.
- Plan limit (
error: "PLAN_LIMIT_REACHED") — retrying will not help until the plan is upgraded or the month rolls over.
Batch where you can: one CSV import is far cheaper than a thousand individual calls.
Error codes
Errors use standard HTTP status codes with a consistent JSON body.
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Missing X-Attrevo-API-Key header"
}| Status | Meaning | What to do |
|---|---|---|
| 400 | Validation failed | Read the message — it names the offending field. |
| 401 | Missing or invalid credential | Check the header name and that the key is not revoked. |
| 403 | Authenticated but not permitted | Often no tenant yet — call POST /api/tenants/bootstrap. |
| 404 | Not found, or wrong method on a valid path | Confirm the verb: a GET on a POST-only route also returns 404. |
| 429 | Rate or plan limit | See the section above. |
| 500 | Server error | Safe to retry idempotent calls; contact support if it persists. |