Drops pour les jeux
Récompensez dans votre jeu les spectateurs qui regardent des diffusions en direct sur Woupi : campagnes, liaison de comptes, droits et livraison.
Vue d'ensemble
Les Drops permettent à un studio de jeu de récompenser les spectateurs de Woupi avec des objets en jeu. Vous créez une campagne liée à la catégorie Woupi de votre jeu; les spectateurs qui regardent des diffusions admissibles accumulent du temps de visionnement, réclament les récompenses débloquées, et votre jeu les leur remet.
Le parcours complet :
- Un spectateur connecté sur woupi.buzz regarde une chaîne en direct qui diffuse votre jeu pendant que votre campagne est active.
- Chaque minute de lecture réelle ajoute une minute de progression à chaque récompense de la campagne.
- Lorsqu'une récompense atteint son nombre de minutes requis, le spectateur la réclame sur woupi.buzz/drops. Cela crée un droit (entitlement) au statut
CLAIMED. - Woupi envoie un événement
drop.entitlement.grantà votre webhook, et le droit devient consultable avecGET /v1/drops/entitlements. - Votre jeu remet l'objet au joueur lié à ce compte Woupi, puis marque le droit
FULFILLEDavecPATCH /v1/drops/entitlements.
Prérequis
- Un compte développeur approuvé : faites votre demande sur dev.woupi.buzz/apply et attendez l'approbation d'un administrateur.
- Une application créée dans le portail. Les campagnes appartiennent à l'une de vos applications, et seule cette application peut lire et livrer leurs droits.
- Une catégorie Woupi qui correspond à votre jeu. Une campagne cible exactement une catégorie; seules les diffusions dans cette catégorie comptent.
- Pour lier des comptes depuis un client de jeu : le flux par code d'appareil et la portée
drops:read(voir Authentification).
Remarque
Un jeu installé sur l'ordinateur des joueurs ne peut pas garder de secret. Enregistrez-le comme application publique pour le flux par code d'appareil, et gardez les appels de serveur à serveur (jeton d'application, webhooks, livraison) sur une application confidentielle ou sur votre propre serveur.
Cycle de vie d'une campagne
Créez vos campagnes dans le portail, sous Drops, avec « Nouvelle campagne ». Une campagne comporte une application, une catégorie, un nom, une description, des dates de début et de fin (saisies à votre heure locale, enregistrées en UTC), une URL de liaison de compte facultative et une liste de chaînes autorisées facultative, ainsi que ses récompenses (nom, minutes requises, image).
| Statut | Signification |
|---|---|
draft | Brouillon en cours de rédaction. Modifiable. Invisible pour les spectateurs. |
pending | Soumise pour examen avec « Soumettre pour approbation ». Non modifiable pendant l'examen. |
approved | Acceptée par un administrateur. Active entre ses dates de début et de fin. N'est plus modifiable; elle peut seulement être terminée plus tôt. |
rejected | Refusée, avec une note d'examen envoyée par courriel. Modifiable; corrigez-la et soumettez-la de nouveau. |
Seules les campagnes draft et rejected sont modifiables. Une campagne approuvée peut seulement être terminée plus tôt avec « Terminer maintenant », ce qui fixe sa date de fin à l'heure actuelle.
| Limite | Valeur |
|---|---|
| Récompenses par campagne | 1 à 10 |
| Minutes de visionnement requises par récompense | 1 à 1440 |
| Durée de la campagne | Au plus 90 jours; la fin doit suivre le début |
| Liste de chaînes autorisées | Facultative, jusqu'à 50 chaînes. Si elle est définie, seules ces chaînes comptent. |
| URL de liaison de compte | Facultative, https seulement. Affichée aux spectateurs sous forme de bouton sur la page des drops. |
| Image de récompense | PNG, JPEG ou WebP, 5 Mo au maximum |
Votre application peut lister ses campagnes et leurs récompenses avec GET /v1/drops/campaigns (jeton d'application). Consultez la référence de l'API.
Accumulation du temps de visionnement
La progression est comptée par spectateur et par récompense. Le lecteur du spectateur envoie un signal toutes les 60 secondes, et chaque signal accepté ajoute 1 minute. Un signal est accepté seulement si toutes les conditions suivantes sont remplies :
- Le spectateur est connecté sur woupi.buzz.
- La vidéo joue réellement : elle n'est pas en pause et suffisamment de données sont chargées. Les rediffusions (VOD) et les clips ne comptent pas.
- La chaîne est en direct, diffuse dans la catégorie de la campagne et figure dans la liste de chaînes autorisées si la campagne en a une.
- La campagne est approuvée et l'heure actuelle se situe entre ses dates de début et de fin.
- Au moins 55 secondes se sont écoulées depuis le dernier signal accepté du spectateur, tous onglets et toutes chaînes confondus. Regarder plusieurs diffusions à la fois n'accélère pas la progression.
Chaque signal compte pour toutes les récompenses que le spectateur n'a pas encore réclamées, dans toutes les campagnes admissibles de la chaîne, en parallèle : une récompense de 30 minutes et une de 60 minutes progressent ensemble. Lorsqu'une récompense atteint ses minutes requises, le spectateur reçoit une notification sur Woupi qui l'invite à la réclamer.
Réclamation par les spectateurs
Les spectateurs voient leurs campagnes, leurs barres de progression et leurs récompenses sur woupi.buzz/drops. La page affiche aussi votre bouton de liaison de compte si vous avez fourni une URL de liaison, pour que les joueurs puissent lier leur compte Woupi à votre jeu.
- Une récompense peut être réclamée dès que sa progression atteint les minutes requises.
- Les réclamations sont acceptées pendant la campagne et jusqu'à 7 jours après sa fin.
- Chaque spectateur ne peut réclamer chaque récompense qu'une seule fois.
- Une réclamation crée un droit au statut
CLAIMEDet émetdrop.entitlement.grantvers votre application.
Remarque
Le spectateur n'a pas besoin d'avoir lié un compte de jeu avant de réclamer. Laissez les droits non liés au statut CLAIMED et livrez-les lorsque le joueur lie son compte.
Liaison de compte par code d'appareil
Pour savoir à quel joueur appartient un droit, votre jeu lie le compte Woupi du joueur avec le flux par code d'appareil et la portée drops:read. Ce flux fonctionne sur tout appareil, y compris les consoles, sans navigateur dans le jeu.
- Le jeu demande un code d'appareil.
- Le jeu affiche le
user_codeet l'adresse woupi.buzz/activate (ou un code QR deverification_uri_complete). - Le joueur se connecte sur Woupi, saisit le code et autorise l'accès.
- Pendant ce temps, le jeu interroge le point de terminaison des jetons toutes les
intervalsecondes jusqu'à recevoir les jetons. - Le jeu appelle le point de terminaison de validation pour lire le
user_idWoupi du joueur et l'enregistre avec le compte de jeu du joueur.
Étape 1 : demander un code d'appareil.
curl -s -X POST https://api.woupi.buzz/oauth2/device \
-d client_id=YOUR_CLIENT_ID \
-d scope=drops:read{
"device_code": "wpd_3q2Vx0bG0y7kJmYw1uQ8aPz4Lr9sTn6cHd5eFi2oXk",
"user_code": "KMPR-7XWD",
"verification_uri": "https://woupi.buzz/activate",
"verification_uri_complete": "https://woupi.buzz/activate?code=KMPR-7XWD",
"expires_in": 1800,
"interval": 5
}Étape 2 : interroger le point de terminaison des jetons toutes les 5 secondes avec le code d'appareil.
curl -s -X POST https://api.woupi.buzz/oauth2/token \
-d client_id=YOUR_CLIENT_ID \
-d grant_type=urn:ietf:params:oauth:grant-type:device_code \
-d device_code=wpd_3q2Vx0bG0y7kJmYw1uQ8aPz4Lr9sTn6cHd5eFi2oXkerror | Que faire |
|---|---|
authorization_pending | Le joueur n'a pas encore répondu. Continuez d'interroger au même intervalle. |
slow_down | Vous avez interrogé avant la fin de interval. Attendez plus longtemps avant la prochaine requête. |
access_denied | Le joueur a refusé. Cessez d'interroger. |
expired_token | Le code a expiré après 30 minutes. Recommencez avec un nouveau code d'appareil. |
Dès que le joueur autorise l'accès, la réponse contient les jetons :
{
"access_token": "wpa_Jx8cQm2VtN5rLw0bZy7Hd4Ks1Pf9Ge6Ua3Oi2Te5Yq",
"refresh_token": "wpr_Tb6nWq1Lc8Zr3Vy5Mk0Hs7Dg2Xp9Fj4Ea1Uo6Ri8Ck",
"expires_in": 14400,
"scope": "drops:read",
"token_type": "bearer"
}Étape 3 : lire l'identité Woupi du joueur.
curl -s https://api.woupi.buzz/oauth2/validate \
-H "Authorization: OAuth wpa_Jx8cQm2VtN5rLw0bZy7Hd4Ks1Pf9Ge6Ua3Oi2Te5Yq"{
"client_id": "k3v9x2m7q1w8e4r6t0y5u2i9o3p7a1",
"login": "stellar_fox",
"user_id": "1042",
"scopes": ["drops:read"],
"expires_in": 14398
}Enregistrez user_id avec le compte de jeu du joueur : c'est le user_id présent dans les droits. Conservez le jeton d'actualisation si le client de jeu lit lui-même ses droits avec ce jeton d'utilisateur; sinon, la liaison suffit, car votre serveur lit tous les droits avec un jeton d'application.
Recevoir drop.entitlement.grant
Le moyen le plus rapide d'apprendre qu'une réclamation a eu lieu est un abonnement webhook à drop.entitlement.grant. C'est un événement au niveau de l'application : vous recevez seulement les droits des campagnes de votre propre application. La condition facultative campaign_id limite l'abonnement à une campagne; avec une condition vide, vous recevez toutes les campagnes de l'application.
Les abonnements webhook exigent un jeton d'application (identifiants client) :
curl -s -X POST https://api.woupi.buzz/oauth2/token \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET \
-d grant_type=client_credentials
curl -s -X POST https://api.woupi.buzz/v1/eventsub/subscriptions \
-H "Authorization: Bearer APP_ACCESS_TOKEN" \
-H "Client-Id: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"type": "drop.entitlement.grant",
"version": "1",
"condition": { "campaign_id": "12" },
"transport": {
"method": "webhook",
"callback": "https://game.example.com/woupi/webhook",
"secret": "change-me-to-a-long-random-secret"
}
}'Chaque réclamation est livrée sous forme de notification :
{
"subscription": {
"id": "5f1c2b7e-8a4d-4c3b-9e21-6d0f7a8b9c10",
"type": "drop.entitlement.grant",
"version": "1",
"status": "enabled",
"condition": { "campaign_id": "12" },
"transport": { "method": "webhook" },
"created_at": "2026-09-20T14:02:11.000Z"
},
"event": {
"entitlement_id": "9b2f6c1e-4d3a-4f7b-8c55-0e1a2b3c4d5e",
"campaign_id": "12",
"reward_id": "34",
"reward_name": "Golden Helmet",
"category_id": "7",
"category_name": "Space Raiders",
"user_id": "1042",
"user_login": "stellar_fox",
"user_name": "StellarFox",
"claimed_at": "2026-09-24T18:30:05.000Z"
}
}Attention
Vérifiez toujours l'en-tête Woupi-Message-Signature et répondez au défi de vérification avant de vous fier à une notification. Consultez Webhooks pour les signatures, les nouvelles tentatives et la révocation.
Lire les droits
GET https://api.woupi.buzz/v1/drops/entitlements liste les droits, du plus ancien au plus récent. Avec un jeton d'application, il renvoie tous les droits de votre application; avec un jeton d'utilisateur qui possède drops:read, il renvoie seulement les droits de cet utilisateur pour votre application.
| Paramètre | Description |
|---|---|
user_id | Seulement les droits de cet utilisateur Woupi. |
campaign_id | Seulement les droits de cette campagne. |
fulfillment_status | CLAIMED (pas encore livré) ou FULFILLED. |
first | Taille de la page, de 1 à 100, 20 par défaut. |
after | Curseur tiré de pagination.cursor de la page précédente. |
curl -s "https://api.woupi.buzz/v1/drops/entitlements?fulfillment_status=CLAIMED&first=100" \
-H "Authorization: Bearer APP_ACCESS_TOKEN" \
-H "Client-Id: YOUR_CLIENT_ID"{
"data": [
{
"id": "9b2f6c1e-4d3a-4f7b-8c55-0e1a2b3c4d5e",
"campaign_id": "12",
"reward_id": "34",
"reward_name": "Golden Helmet",
"user_id": "1042",
"fulfillment_status": "CLAIMED",
"is_test": false,
"claimed_at": "2026-09-24T18:30:05.000Z",
"fulfilled_at": null
}
],
"pagination": { "cursor": "MTI4" }
}Lorsque pagination.cursor est présent, passez-le dans after pour obtenir la page suivante. Lorsqu'il est absent, vous avez atteint la fin de la liste.
Livrer les droits
Une fois l'objet dans l'inventaire du joueur, marquez le droit FULFILLED. Vous pouvez mettre à jour jusqu'à 100 droits par requête, avec un jeton d'application ou un jeton d'utilisateur qui possède drops:read. Remettre CLAIMED annule une livraison, par exemple après un échec de votre côté.
curl -s -X PATCH https://api.woupi.buzz/v1/drops/entitlements \
-H "Authorization: Bearer APP_ACCESS_TOKEN" \
-H "Client-Id: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"entitlement_ids": [
"9b2f6c1e-4d3a-4f7b-8c55-0e1a2b3c4d5e",
"00000000-0000-4000-8000-000000000000"
],
"fulfillment_status": "FULFILLED"
}'{
"data": [
{ "status": "SUCCESS", "ids": ["9b2f6c1e-4d3a-4f7b-8c55-0e1a2b3c4d5e"] },
{ "status": "NOT_FOUND", "ids": ["00000000-0000-4000-8000-000000000000"] }
]
}| Statut | Signification |
|---|---|
SUCCESS | Le droit a maintenant le statut demandé. |
NOT_FOUND | Aucun droit portant cet identifiant n'appartient à votre application. |
UNAUTHORIZED | Le droit existe, mais ce jeton ne peut pas le modifier, par exemple un jeton d'utilisateur visant le droit d'un autre utilisateur. |
Les spectateurs voient les récompenses livrées comme telles sur leur page des drops.
Algorithme recommandé côté jeu
- Liez les comptes avec le flux par code d'appareil et enregistrez le
user_idWoupi de chaque joueur. - Traitez chaque droit à la réception d'une notification
drop.entitlement.grant, et périodiquement avecGET /v1/drops/entitlements?fulfillment_status=CLAIMEDcomme solution de repli au cas où un webhook serait manqué. - Remettez l'objet une seule fois, en vous fiant à l'
iddu droit : enregistrez l'identifiant dans votre base de données avec une contrainte d'unicité, dans la même transaction que la remise de l'objet. Une notification en double n'a alors aucun effet. - Appelez
PATCHavecFULFILLED. Si l'appel échoue, réessayez plus tard : l'objet est déjà remis, et redemander le même statut est sans danger. - Si le
user_iddu droit n'est encore lié à aucun joueur, laissez-leCLAIMEDet traitez-le lorsque le joueur liera son compte.
const API = 'https://api.woupi.buzz/v1';
const headers = {
Authorization: `Bearer ${process.env.WOUPI_APP_TOKEN}`,
'Client-Id': process.env.WOUPI_CLIENT_ID,
'Content-Type': 'application/json',
};
async function processEntitlement(ent) {
if (ent.is_test && process.env.NODE_ENV === 'production') return;
const player = await db.findPlayerByWoupiUserId(ent.user_id);
if (!player) return; // not linked yet: stays CLAIMED, retried later
// Grants the item and records ent.id (unique key) in one transaction.
// Returns without granting again when ent.id was already recorded.
await db.grantRewardOnce({ entitlementId: ent.id, playerId: player.id, rewardId: ent.reward_id });
const res = await fetch(`${API}/drops/entitlements`, {
method: 'PATCH',
headers,
body: JSON.stringify({ entitlement_ids: [ent.id], fulfillment_status: 'FULFILLED' }),
});
if (!res.ok) throw new Error(`PATCH failed: ${res.status}`); // retry on next pass
}
// Fallback: run every few minutes in addition to the webhook.
async function pollClaimed() {
let after = '';
do {
const qs = new URLSearchParams({ fulfillment_status: 'CLAIMED', first: '100' });
if (after) qs.set('after', after);
const res = await fetch(`${API}/drops/entitlements?${qs}`, { headers });
const body = await res.json();
for (const ent of body.data) await processEntitlement(ent);
after = body.pagination.cursor || '';
} while (after);
}Remarque
Les notifications webhook transmettent l'identifiant du droit dans event.entitlement_id; les objets de droit de l'API REST le transmettent dans id.
Tests
Sur la page d'une campagne dans le portail, la zone « Tester » accorde un droit de test pour la récompense choisie à votre propre compte Woupi, quel que soit le statut de la campagne (même en brouillon). Le parcours est complet : un événement drop.entitlement.grant est envoyé à vos abonnements et le droit apparaît dans GET /v1/drops/entitlements.
- Les droits de test ont
is_test: true, dans l'événement comme dans les objets REST. - Liez votre propre compte Woupi dans votre jeu pour tester la livraison complète, puis vérifiez que
PATCHrenvoieSUCCESS. - La page de la campagne affiche aussi des statistiques par récompense (spectateurs en progression, droits réclamés, droits livrés) et un tableau des droits.
Attention
Ignorez les droits avec is_test: true en production, ou livrez-les seulement aux comptes de votre équipe.
