FirstMRRCentre d'aide
Développeurs

API des événements serveur

Envoie tes conversions depuis ton backend avec une clé secrète : schéma, réponses, erreurs, idempotence et limites.

4 min de lecture

Point d'entrée

POSThttps://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

ChampTypeRequisDescription
typestringObligatoireParmi signup, activation, trial, payment, cancel, source, exposure, custom.
event_idstringObligatoireTon 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_atstringOptionnelDate ISO 8601 avec fuseau (2026-10-12T14:03:00Z), dans les 72 dernières heures. Par défaut : la réception.
visitor_idstringOptionnelLa 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_centsintegerOptionnelPaiement seulement : le montant en centimes (1999 pour 19,99).
currencystringOptionnelCode ISO 4217 à 3 lettres, obligatoire avec value_cents.
sourceobjectOptionnelLa 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é).
variantsstring[]OptionnelLes affectations A/B du visiteur, "test:variante", 4 au plus. Obligatoire pour exposure.
namestringOptionnelÉvénement custom seulement : son nom, 1 à 40 lettres minuscules, chiffres ou _.
answerstringOptionnelÉ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.
accountstringOptionnelÉ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.

json
{
  "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" } } :

CodeHTTPDescription
missing_key401Send your secret key in the Authorization header: Bearer sk_live_…
invalid_key401This secret key does not exist or was revoked.
invalid_json400The body is not valid JSON.
body_too_large400The body is larger than 64 KB.
invalid_body400The body must be an object with an events array, and nothing else.
empty_batch400The events array is empty.
too_many_events400Send 100 events per request at most.
rate_limited429Too many requests: 600 per 15 minutes per account. Retry after the Retry-After delay.
unavailable503The events API is momentarily unavailable. Retry later.

Une erreur d'événement ne refuse que cet événement, dans rejected :

CodePortéeDescription
invalid_eventévénementThe event must be an object.
unknown_fieldévénementThis field is not part of the schema (personal data such as e-mails or names is never accepted).
invalid_typeévénementtype must be one of signup, activation, trial, payment, cancel, source, exposure, custom.
invalid_event_idévénementevent_id is required: 1 to 64 letters, digits, _ . : or -.
invalid_occurred_atévénementoccurred_at must be an ISO 8601 date with a time zone, within the last 72 hours.
invalid_visitor_idévénementvisitor_id must be 8 to 64 letters, digits or dashes (firstmrr.visitorId() or the app's install id).
invalid_valueévénementvalue_cents must be a whole number of cents, from 0 to 9 999 999, on a payment only.
invalid_currencyévénementcurrency must be a 3-letter ISO 4217 code, given with value_cents.
invalid_sourceévénementsource must be an object with method among link, utm, in_app, referrer and its platform or ref.
invalid_variantsévénementvariants must be a list of 1 to 4 "test:variant" strings, one per test.
invalid_nameévénementname is required on a custom event, only there: 1 to 40 lower-case letters, digits or _.
invalid_answerévénementanswer 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énementaccount (a source event only) must be a social handle or a name of 60 characters at most, never an e-mail.
daily_capévénementThe 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à, 429 avec l'en-tête Retry-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_cap jusqu'à 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.