API des événements serveur
Envoie tes conversions depuis ton backend avec une clé secrète : schéma, réponses, erreurs, idempotence et limites.
Point d'entrée
https://firstmrr.app/api/v1/eventsAuthorization: Bearer sk_live_…Un corps JSON { "events": [...] } avec 1 à 100 événements, 64 Ko au plus. Chaque événement est accepté ou refusé seul : un événement invalide n'empêche pas les autres.
curl https://firstmrr.app/api/v1/events \
-H "Authorization: Bearer $FIRSTMRR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"events":[{"type":"signup","event_id":"user_8412","visitor_id":"6f1c2b9e-1d4a-4c55-9a3e-2b7d0c9e8f11","source":{"method":"link","ref":"tiktok-oscar-3f9a"}},{"type":"payment","event_id":"in_1PqXyZ","value_cents":1999,"currency":"EUR","visitor_id":"6f1c2b9e-1d4a-4c55-9a3e-2b7d0c9e8f11","variants":["pricing_page:b"]}]}'Un événement
| Champ | Type | Requis | Description |
|---|---|---|---|
type | string | Obligatoire | Parmi signup, activation, trial, payment, cancel, source, exposure, custom. |
event_id | string | Obligatoire | Ton identifiant unique de l'événement (1 à 64 caractères : lettres, chiffres, _ . : -). Le même identifiant n'est compté qu'une fois : c'est la clé d'idempotence. Pour un paiement, prends l'identifiant de la facture ou de la commande, le même que dans track("payment", { id }). |
occurred_at | string | Optionnel | Date ISO 8601 avec fuseau (2026-10-12T14:03:00Z), dans les 72 dernières heures. Par défaut : la réception. |
visitor_id | string | Optionnel | La valeur de firstmrr.visitorId() dans le navigateur, ou l'identifiant d'installation de ton app (8 à 64 lettres, chiffres ou tirets). Rattache l'événement au visiteur du même jour. |
value_cents | integer | Optionnel | Paiement seulement : le montant en centimes (1999 pour 19,99). |
currency | string | Optionnel | Code ISO 4217 à 3 lettres, obligatoire avec value_cents. |
source | object | Optionnel | La provenance renvoyée par firstmrr.source() à l'inscription, telle quelle : method parmi link, utm, in_app, referrer, avec platform ou ref (at est accepté et ignoré). |
variants | string[] | Optionnel | Les affectations A/B du visiteur, "test:variante", 4 au plus. Obligatoire pour exposure. |
name | string | Optionnel | Événement custom seulement : son nom, 1 à 40 lettres minuscules, chiffres ou _. |
answer | string | Optionnel | Événement source seulement : la réponse à « Où nous as-tu connus ? », parmi tiktok, instagram, youtube, x, threads, facebook, linkedin, snapchat, pinterest, google, app_store, friend, press, other. |
account | string | Optionnel | Événement source seulement : le compte que la personne cite (« @compte »), 60 caractères au plus. Une adresse e-mail est refusée. |
Réponse
202 Accepted dès que la requête est lue : accepted compte les événements enregistrés, duplicates ceux déjà reçus avec le même event_id, et rejected donne, pour chaque refus, sa position dans ta requête, un code et le champ en cause.
{
"accepted": 1,
"duplicates": 1,
"rejected": [
{
"index": 2,
"error": "unknown_field",
"field": "email",
"message": "This field is not part of the schema (personal data such as e-mails or names is never accepted)."
}
]
}Erreurs
Une erreur de requête refuse toute la requête, avec { "error": { "code", "message" } } :
| Code | HTTP | Description |
|---|---|---|
missing_key | 401 | Send your secret key in the Authorization header: Bearer sk_live_… |
invalid_key | 401 | This secret key does not exist or was revoked. |
invalid_json | 400 | The body is not valid JSON. |
body_too_large | 400 | The body is larger than 64 KB. |
invalid_body | 400 | The body must be an object with an events array, and nothing else. |
empty_batch | 400 | The events array is empty. |
too_many_events | 400 | Send 100 events per request at most. |
rate_limited | 429 | Too many requests: 600 per 15 minutes per account. Retry after the Retry-After delay. |
unavailable | 503 | The events API is momentarily unavailable. Retry later. |
Une erreur d'événement ne refuse que cet événement, dans rejected :
| Code | Portée | Description |
|---|---|---|
invalid_event | événement | The event must be an object. |
unknown_field | événement | This field is not part of the schema (personal data such as e-mails or names is never accepted). |
invalid_type | événement | type must be one of signup, activation, trial, payment, cancel, source, exposure, custom. |
invalid_event_id | événement | event_id is required: 1 to 64 letters, digits, _ . : or -. |
invalid_occurred_at | événement | occurred_at must be an ISO 8601 date with a time zone, within the last 72 hours. |
invalid_visitor_id | événement | visitor_id must be 8 to 64 letters, digits or dashes (firstmrr.visitorId() or the app's install id). |
invalid_value | événement | value_cents must be a whole number of cents, from 0 to 9 999 999, on a payment only. |
invalid_currency | événement | currency must be a 3-letter ISO 4217 code, given with value_cents. |
invalid_source | événement | source must be an object with method among link, utm, in_app, referrer and its platform or ref. |
invalid_variants | événement | variants must be a list of 1 to 4 "test:variant" strings, one per test. |
invalid_name | événement | name is required on a custom event, only there: 1 to 40 lower-case letters, digits or _. |
invalid_answer | événement | answer is required on a source event, only there: one of tiktok, instagram, youtube, x, threads, facebook, linkedin, snapchat, pinterest, google, app_store, friend, press, other. |
invalid_account | événement | account (a source event only) must be a social handle or a name of 60 characters at most, never an e-mail. |
daily_cap | événement | The site reached its daily event cap: retry after 00:00 UTC. |
Idempotence et nouvelles tentatives
Un event_id déjà reçu pour le même type d'événement n'est jamais compté deux fois, qu'il vienne de l'API ou du script (track("payment", { id })). Après une erreur réseau, un 503 ou un 429, renvoie donc le même lot sans crainte : les événements déjà enregistrés reviennent en duplicates.
Limites
- 600 requêtes par tranche de 15 minutes pour l'ensemble de tes clés ; au-delà,
429avec l'en-têteRetry-After(en secondes). - 100 événements par requête : regroupe plutôt que d'envoyer un appel par événement.
- 3 000 événements par jour et par site ou app, script compris ; au-delà, les événements reviennent en
daily_capjusqu'à minuit UTC.
Relier un événement serveur à son visiteur
Sans visitor_id, l'événement est compté seul. Avec la valeur de firstmrr.visitorId() (site) ou l'identifiant d'installation (app), il rejoint le visiteur du même jour : ses visites, sa provenance et ses variantes A/B. Passe aussi source (la provenance enregistrée à l'inscription) et variants quand tu les as : ce sont eux qui rattachent un paiement serveur à son canal et à sa variante.