Référence de l'API
Événements
Le journal de tout ce qui s'est passé dans une application, et chaque type d'événement.
Un événement enregistre quelque chose qui s'est produit dans votre application, comme une facture payée. Abuna enregistre chaque événement, et l'envoie aussi à votre URL de webhook si vous en avez défini une.
L'objet event
Un événement a la même forme ici et dans un corps de webhook. Un webhook pour l'événement porte le même id.
Attributs
idstringIdentifiant unique de l'événement. Les ID d'événement se trient dans l'ordre où les événements ont eu lieu. Voir Ordre des événements.typestringLe type de l'événement.environmentstringtestoulive.created_attimestampQuand l'événement a eu lieu, en secondes Unix.dataobjectLes données de l'événement. Leurs champs dépendent du type.
{
"created_at": 1791143442,
"data": {
"amount": 5000,
"credit": 0,
"currency": "XAF",
"invoice_id": "hUPYtkyo",
"invoice_number": "ACME-000001",
"kind": "period",
"metadata": {
"user_id": "42"
},
"paid_at": 1791143442,
"period_end": 1793821842,
"period_id": "tKhKXYjr",
"period_start": 1791143442,
"subscription": {
"activated_at": 1791143442,
"app_id": "VyYjSLf1",
"cancel_at": null,
"canceled_at": null,
"created_at": 1791143442,
"current_period_end": 1793821842,
"current_period_start": 1791143442,
"customer_id": "OljRiqBE",
"ended_reason": null,
"id": "coRNVNUz",
"manage_token": "d6f6ca3a8b285784aed37bd593bab9dea12e",
"metadata": {
"user_id": "42"
},
"price_id": "UKjuDVyN",
"starts_at": 1791143442,
"status": "active"
},
"subscription_id": "coRNVNUz"
},
"environment": "test",
"id": "01M447FXSP98350QH3TKE3YG6D",
"type": "invoice.paid"
}Lister les événements
GET/v1/apps/{appID}/events
Liste les événements de l'application, du plus récent au plus ancien, une page à la fois. Chaque page continue avec des événements plus anciens. Voir Listes et filtres.
Paramètres de requête
typestringUniquement les événements de ce type, commeinvoice.paid.limitintegerCombien d'événements renvoyer, de 1 à 100. Par défaut : 50.cursorstringLa valeurnext_cursorde la page précédente. Envoyez les mêmes filtres avec.
Attributs de la réponse
itemsarrayLes événements de la page, du plus récent au plus ancien. Vide quand rien ne correspond.next_cursorstringnullablePassez-le comme cursor pour obtenir la page suivante, plus ancienne. Il vaut null sur la dernière page. Traitez-le comme opaque.
Renvoie 200. Un limit en dehors de 1 à 100 renvoie 422 avec le code invalid et un élément errors pour limit.
curl "https://api.abuna.app/v1/apps/8ddhXCDW/events?type=invoice.paid" \
-H "Authorization: Bearer sk_test_..."{
"items": [
{
"created_at": 1791143442,
"data": {
"amount": 5000,
"credit": 0,
"currency": "XAF",
"invoice_id": "hUPYtkyo",
"invoice_number": "ACME-000001",
"kind": "period",
"metadata": {
"user_id": "42"
},
"paid_at": 1791143442,
"period_end": 1793821842,
"period_id": "tKhKXYjr",
"period_start": 1791143442,
"subscription": {
"activated_at": 1791143442,
"app_id": "VyYjSLf1",
"cancel_at": null,
"canceled_at": null,
"created_at": 1791143442,
"current_period_end": 1793821842,
"current_period_start": 1791143442,
"customer_id": "OljRiqBE",
"ended_reason": null,
"id": "coRNVNUz",
"manage_token": "d6f6ca3a8b285784aed37bd593bab9dea12e",
"metadata": {
"user_id": "42"
},
"price_id": "UKjuDVyN",
"starts_at": 1791143442,
"status": "active"
},
"subscription_id": "coRNVNUz"
},
"environment": "test",
"id": "01M447FXSP98350QH3TKE3YG6D",
"type": "invoice.paid"
}
],
"next_cursor": "MDFKOVpROEUxRjJHM0g0SjVLNk03TjhQOVE"
}Récupérer un événement
GET/v1/apps/{appID}/events/{id}
Récupère un événement, dans la même forme que le corps de son webhook. Utilisez-le pour récupérer à nouveau un événement par l'id qu'un webhook vous a donné.
Renvoie l'événement. Si l'application n'a aucun événement avec cet ID, renvoie 404 avec le code not_found.
curl https://api.abuna.app/v1/apps/8ddhXCDW/events/01M447FXSP98350QH3TKE3YG6D \
-H "Authorization: Bearer sk_test_..."{
"created_at": 1791143442,
"data": {
"amount": 5000,
"credit": 0,
"currency": "XAF",
"invoice_id": "hUPYtkyo",
"invoice_number": "ACME-000001",
"kind": "period",
"metadata": {
"user_id": "42"
},
"paid_at": 1791143442,
"period_end": 1793821842,
"period_id": "tKhKXYjr",
"period_start": 1791143442,
"subscription": {
"activated_at": 1791143442,
"app_id": "VyYjSLf1",
"cancel_at": null,
"canceled_at": null,
"created_at": 1791143442,
"current_period_end": 1793821842,
"current_period_start": 1791143442,
"customer_id": "OljRiqBE",
"ended_reason": null,
"id": "coRNVNUz",
"manage_token": "d6f6ca3a8b285784aed37bd593bab9dea12e",
"metadata": {
"user_id": "42"
},
"price_id": "UKjuDVyN",
"starts_at": 1791143442,
"status": "active"
},
"subscription_id": "coRNVNUz"
},
"environment": "test",
"id": "01M447FXSP98350QH3TKE3YG6D",
"type": "invoice.paid"
}Ordre des événements
Les ID d'événement se trient dans l'ordre où les événements ont eu lieu. Comparez-les comme de simples chaînes : l'ID d'un événement plus tardif se trie après celui d'un événement plus ancien. created_at est en secondes entières, donc les événements d'un même changement le partagent souvent. Ordonnez les événements par id, pas par created_at.
Un même changement enregistre plusieurs événements à la fois. Ils arrivent dans cet ordre :
- Nouvel abonnement :
subscription.created, puisinvoice.created. - Renouvellement :
subscription.updatedsi un passage à un tarif inférieur programmé commence avec la nouvelle période,subscription.renewed,subscription.past_duesi la nouvelle période a déjà commencé, puisinvoice.created. Un renouvellement émis avant la fin de la période payée envoiesubscription.past_duede son côté plus tard, à la fin de la période payée, s'il est encore impayé à ce moment-là. - Paiement :
subscription.updateds'il paie une montée de formule,session.completeds'il a été payé via une sessionpaymentou termine une sessionplan_change,invoice.paid, puis au premier paiementsubscription.paidet lesession.completeddu checkout, et enfinsubscription.recoveredsi l'abonnement étaitpast_due.
Rattraper les événements manqués
Si votre serveur a manqué des webhooks, listez les événements que vous n'avez pas traités et traitez-les maintenant. Commencez sans cursor, puis passez le next_cursor de chaque page jusqu'à ce qu'il soit null, ou jusqu'à atteindre des événements créés 5 minutes avant le plus récent que vous avez traité. Un événement peut être listé quelques secondes après un plus récent, donc ignorez ceux que vous avez déjà traités par id plutôt que de vous arrêter au premier. Traitez ce que vous avez collecté du plus ancien au plus récent.
const base = "https://api.abuna.app/v1/apps/8ddhXCDW/events?limit=100";
const headers = { Authorization: "Bearer sk_test_..." };
const newestHandledAt = 1727600600; // created_at of the newest event you handled
const stopAt = newestHandledAt - 5 * 60;
const missed = [];
let cursor = null;
let done = false;
while (!done) {
const url = base + (cursor ? "&cursor=" + encodeURIComponent(cursor) : "");
const page = await (await fetch(url, { headers })).json();
for (const event of page.items) {
if (event.created_at < stopAt) {
done = true;
break;
}
if (!(await alreadyHandled(event.id))) missed.push(event);
}
cursor = page.next_cursor;
if (!cursor) done = true;
}
// Oldest first, in the order the events happened.
for (const event of missed.reverse()) {
await handle(event);
}Types d'événement
Chaque type ci-dessous liste les champs de son data. Les ID sont des chaînes, les montants sont des entiers dans la plus petite unité de la devise, et les heures sont en secondes Unix.
Chaque événement dont le data contient un subscription_id contient aussi subscription : l'objet subscription complet, tel que Récupérer un abonnement le renvoie sous subscription, tel qu'il est juste après le changement que l'événement signale. Un événement de session dont le subscription_id est null n'a pas de subscription. Les listes ci-dessous omettent subscription.
Les événements d'abonnement, de facture et d'offre de formule portent les métadonnées de l'abonnement au moment où l'événement est enregistré. Les événements de session portent lemetadata propre à la session, pas celui de l'abonnement. Passez du metadata à chaque session que vous créez. Sans cela, les événements de session portent {}. Les listes ci-dessous omettent le metadata sauf indication contraire.
session.completed
Une session s'est terminée. Une session se termine au plus une fois, et une session expirée ne se termine jamais. Le data dépend du type de la session. Chaque type a session_id, type, subscription_id, customer_id, metadata (le metadata que vous avez passé à la création de la session) et completed_at.
checkout(Créer une session checkout) : le premier paiement a réussi et l'abonnement est maintenantactive.subscription.paidest enregistré au même moment. Ajouteprice_id, le tarif de l'abonnement.payment(Créer une session de paiement) : la facture a été payée via la session.invoice.paidest aussi envoyé. Ajouteinvoice_id.plan_change(Créer une session de changement de formule) : le client a confirmé le changement, et l'a payé s'il s'agissait d'une montée de formule avec quelque chose à payer. Ajoute les champs ci-dessous.cancel(Créer une session d'annulation) : le client a confirmé l'annulation, programmée ou immédiate.subscription.updated(programmée) ousubscription.canceled(immédiate) est aussi envoyé. Ajoute les champs ci-dessous.portal(Créer une session d'espace client) : le client a ouvert son espace client pour la première fois. Cela ne signifie pas qu'il a changé quelque chose. Ce qu'il y fait envoie ses propres événements, commesubscription.updated,subscription.canceled,invoice.paidetcustomer.updated. N'ajoute rien.
plan_change data ajoutés
plan_offer_idstringL'offre de formule que la session a créée.previous_price_idstringLe tarif avant le changement.price_idstringLe nouveau tarif.directionstringupgradeoudowngrade.effective_attimestampQuand le nouveau tarif commence. Pour un passage à un tarif inférieur, la fin de la période payée :subscription.updatedse déclenche alors avec le changement de tarif.
cancel data ajoutés
cancel_attimestampnullableQuand l'abonnement prend fin.subscription.canceledse déclenche alors. Null quand il a pris fin tout de suite.immediatebooleantruequand l'abonnement a pris fin tout de suite.
session.expired
Une session ouverte a expiré à son expires_at, ou vous l'avez fait expirer. Abuna ne fait pas expirer une session checkout de lui-même pendant que son paiement est en attente. Un abonnement pending démarré par une session checkout reste, et prend fin avec checkout_expired s'il n'est pas payé à temps.
Une session payment expire aussi quand sa facture est payée autrement, annulée, ou quand son abonnement prend fin. Une session plan_change expire aussi quand son offre est remplacée, annulée ou expirée, ou quand une montée de formule qu'elle a démarrée tombe en désuétude faute de paiement. Une session cancel expire 24 heures après sa création si le client ne confirme pas, et une session portal 1 heure après sa création si personne ne l'ouvre. Voir Types de session.
data
session_idstringLa session.typestringLe type de la session :checkout,payment,plan_change,cancelouportal.subscription_idstringnullableL'abonnement que la session a démarré ou sur lequel elle agit. Pour checkout, null si le client n'est pas allé jusque-là.customer_idstringnullableLe client que vous avez passé, ou celui créé à partir des coordonnées du client. Null si aucun des deux.price_idstringnullableLe tarif de la session, ou null pour les sessions payment, cancel et portal.invoice_idstringnullableLa facture de la session payment, ou null pour les autres types.plan_offer_idstringnullableL'offre de la session plan_change, ou null pour les autres types.metadataobjectLe metadata que vous avez passé à la création de la session.expired_attimestampQuand la session a expiré.
{
"customer_id": "OljRiqBE",
"expired_at": 1791143442,
"invoice_id": null,
"metadata": {
"user_id": "42"
},
"plan_offer_id": null,
"price_id": null,
"session_id": "ErXGrkep",
"subscription": {
"activated_at": 1791143442,
"app_id": "VyYjSLf1",
"cancel_at": null,
"canceled_at": null,
"created_at": 1791143442,
"current_period_end": 1793821842,
"current_period_start": 1791143442,
"customer_id": "OljRiqBE",
"ended_reason": null,
"id": "coRNVNUz",
"manage_token": "d6f6ca3a8b285784aed37bd593bab9dea12e",
"metadata": {
"user_id": "42"
},
"price_id": "UKjuDVyN",
"starts_at": 1791143442,
"status": "active"
},
"subscription_id": "coRNVNUz",
"type": "cancel"
}subscription.created
Un abonnement a été créé, via l'API, une session checkout ou un lien de paiement. Il est encore pending.
data
subscription_idstringLe nouvel abonnement.customer_idstringSon client.price_idstringSon tarif.period_idstringSa première période de facturation.
subscription.paid
La première période de facturation a été payée, donc l'abonnement est maintenant active. Les paiements suivants envoient invoice.paid, et subscription.recovered quand ils ramènent un abonnement past_due à active.
data
subscription_idstringL'abonnement.customer_idstringSon client.price_idstringSon tarif.period_idstringLa période de facturation payée.activated_attimestampQuand l'abonnement est devenu actif.
subscription.renewed
Abuna a émis la période de facturation suivante et sa facture. Cela se produit avant la fin de la période payée, d'autant de jours à l'avance que l'indique renewal_lead_days de l'application (3 par défaut), ou quand elle se termine avec 0. La nouvelle période commence quand la période payée se termine, et le client peut la payer dès maintenant. Si elle est encore impayée à la fin de la période payée, subscription.past_due suit alors.
data
subscription_idstringL'abonnement.period_idstringLa nouvelle période de facturation.amountintegerLe montant dû pour la nouvelle période.currencystringXAF.
subscription.past_due
La période payée a pris fin et son renouvellement n'est pas payé, donc l'abonnement est maintenant past_due. Un renouvellement payé avant la période payée ne l'envoie jamais. Gardez l'accès du client tant qu'il est past_due. Si la période n'est pas payée à sa date limite de paiement, subscription.canceled suit avec la raison non_payment.
data
subscription_idstringL'abonnement.metadataobjectLes métadonnées de l'abonnement.
subscription.recovered
La facture d'un abonnement past_due a été payée, donc il est de nouveau active. Il vient juste après invoice.paid. Il vient aussi quand un paiement qui arrive après une annulation pour non-paiement ramène l'abonnement ; voir Paiements qui arrivent en retard. Le data est le même que celui de subscription.past_due.
subscription.updated
Quelque chose a été programmé ou a changé sur un abonnement actif. Il se déclenche quand une annulation à la fin de la période payée est programmée ou annulée, quand le tarif change (une montée de formule prend effet, ou un passage à un tarif inférieur programmé commence), quand un passage à un tarif inférieur est programmé ou annulé, quand une montée de formule en attente de paiement tombe en désuétude, et quand un nouveau changement en efface un précédent. Pour une annulation programmée, subscription.canceled suit quand elle prend fin. Voir Changer de formule.
data
subscription_idstringL'abonnement.price_idstringLe tarif sur lequel l'abonnement est actuellement.cancel_attimestampnullableQuand l'abonnement prendra fin, si une annulation est programmée. Null sinon.scheduled_price_changeobjectnullableLe passage à un tarif inférieur programmé pour la fin de la période payée. Null quand il n'y en a pas.Afficher les attributsMasquer les attributs
price_idstringLe tarif vers lequel l'abonnement passe.effective_attimestampQuand il passe.
changeobjectCe qui est arrivé au tarif. Présent uniquement quand le tarif a changé ou qu'un passage à un tarif inférieur a été programmé ou annulé. Une montée de formule tombée en désuétude, une annulation à la fin de la période, et une montée de formule qui commence à attendre un paiement n'en ont pas.Afficher les attributsMasquer les attributs
typestringprice_changed,downgrade_scheduledoudowngrade_undone.old_price_idstringLe tarif avant.new_price_idstringLe nouveau tarif, ou celui programmé.effective_attimestampQuand le changement a pris effet, ou quand le changement programmé le fera.
updated_bystringmerchantquand vous l'avez changé, oucustomerquand le client l'a changé dans son espace client. Quand un changement de formule est exécuté plus tard (une montée de formule payée, un passage à un tarif inférieur qui commence, une montée de formule qui tombe en désuétude), c'est celui qui l'a demandé :customerdepuis l'espace client, oumerchantquand il a confirmé un changement vers lequel vous l'avez envoyé.
subscription.canceled
L'abonnement a pris fin. Il se déclenche quand l'abonnement prend vraiment fin : tout de suite pour une annulation immédiate, ou à la fin de la période payée pour une annulation programmée. Programmer une annulation envoie subscription.updated à la place. Le subscription dans le data a ended_reason unpaid pour non_payment et checkout_expired, et canceled pour les autres.
data
subscription_idstringL'abonnement.reasonstringPourquoi il a pris fin :merchant(vous l'avez annulé),customer(le client l'a annulé dans son espace client),non_payment(un renouvellement a dépassé sa date limite de paiement) oucheckout_expired(la première période n'a jamais été payée). Une annulation programmée qui prend fin garde qui l'a programmée,merchantoucustomer.
plan_offer.created
Une session de changement de formule a créé une offre de formule, ou vous avez proposé un changement depuis le tableau de bord et Abuna a envoyé le lien au client. Une session n'envoie rien au client. Renvoyer le lien depuis le tableau de bord ne le déclenche pas à nouveau. Les quatre plan_offer événements partagent le même data.
data
offer_idstringL'offre.subscription_idstringL'abonnement.price_idstringLe tarif proposé.expires_attimestampQuand l'offre cesse de fonctionner.
plan_offer.confirmed
Le client a accepté l'offre. Un passage à un tarif inférieur est maintenant programmé, ou une montée de formule est appliquée ou en attente de paiement. subscription.updated se déclenche quand le tarif change ou que le passage à un tarif inférieur est programmé.
plan_offer.canceled
L'offre a cessé avant que le client l'accepte. Le data ajoute reason : merchant quand vous l'avez annulée, ou replaced quand une nouvelle offre ou le propre changement de formule du client a pris sa place.
plan_offer.expired
L'offre a atteint son expires_at sans être acceptée.
invoice.created
Abuna a émis une facture pour une nouvelle période de facturation, ou pour une montée de formule. Une montée de formule en attente de paiement reçoit les dates de sa période quand elle est payée, et invoice.paid les porte.
data
invoice_idstringLa facture.invoice_numberstringSon numéro.subscription_idstringSon abonnement.period_idstringLa période de facturation qu'elle facture.kindstringperiodpour une période de facturation, ouplan_changepour la première période d'une montée de formule.amountintegerLe montant dû.creditintegerLe temps inutilisé de la période en cours déduit du nouveau tarif.0sur une factureperiod.currencystringXAF.period_starttimestampQuand la période commence.period_endtimestampQuand la période se termine.
invoice.paid
Une facture a été payée. Un renouvellement payé avant le début de sa période est tout de même pour cette période : period_start ne bouge pas. Une facture qui a été annulée peut aussi devenir payée, quand un paiement que le client a approuvé avant son annulation aboutit ; voir Paiements qui arrivent en retard. Le data a tous les champs de invoice.created, plus un.
data
paid_attimestampQuand elle a été payée.
invoice.payment_failed
Un paiement sur une facture a échoué. La facture reste ouverte, donc le client peut réessayer avant la date limite de paiement.
data
subscription_idstringL'abonnement.period_idstringLa période de facturation.invoice_idstringLa facture.invoice_numberstringSon numéro.provider_referencestringL'ID du prestataire pour le débit. Vide s'il n'en a donné aucun.messagestringLe message du prestataire. Vide s'il n'en a donné aucun.
customer.updated
Le nom, l'e-mail ou le numéro de téléphone d'un client a changé.
data
customer_idstringLe client.namestringnullableSon nom maintenant.emailstringSon adresse e-mail maintenant.phone_numberstringSon numéro de téléphone maintenant.metadataobjectLes métadonnées du client.updated_bystringmerchantquand vous l'avez changé, oucustomerquand le client l'a changé dans son espace client.
price_change.scheduled
Vous avez programmé un nouveau montant pour un tarif, ou remplacé celui qui était en attente. Les trois price_change événements partagent le même data.
data
price_change_idstringLe changement.price_idstringLe tarif.amountintegerLe nouveau montant.previous_amountintegerLe montant avant le changement.currencystringXAF.effective_attimestampMinuit au début du jour à partir duquel le nouveau montant s'applique, dans timezone.timezonestringLe fuseau horaire de votre application, commeAfrica/Douala. C'est letimezonede l'application.keep_current_subscribersbooleantruequand toutes les personnes abonnées ce jour-là gardent l'ancien montant.
price_change.canceled
Un changement en attente s'est arrêté avant son jour. Le data ajoute reason : merchant quand vous l'avez annulé, ou replaced quand vous avez programmé un nouveau changement à sa place. Un remplacement envoie aussi price_change.scheduled.
price_change.applied
Le montant du tarif est passé au nouveau à son effective_at. L'événement arrive dans environ une minute après ce moment. À partir de là, les checkouts et les nouveaux abonnements le paient. Les abonnés actuels y passent à leur prochain renouvellement, sauf si keep_current_subscribers est true.
notification.captured
Mode Test uniquement. Abuna a écrit un avis à un client Test et l'a conservé au lieu de l'envoyer. Cet événement ne va jamais vers votre URL de webhook.
data
customer_idstringLe client.kindstringCe sur quoi porte l'avis. Voir types d'avis.channelstringemailoutelegram.recipientstringL'adresse e-mail ou la conversation Telegram à laquelle il serait allé.subjectstringLa ligne d'objet.textstringLe message, en texte brut.sentbooleanToujoursfalse.