Aller au contenu principal

Webhooks

L'API MyTV peut envoyer des notifications webhook vers des systemes externes lorsque certains evenements se produisent. Les webhooks permettent une integration en temps reel avec des services tiers.

Configuration des Webhooks

Enregistrer un Webhook

Les webhooks sont configures dans le panneau d'administration ou via l'API Admin :

POST /api/v2/admin/webhooks
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"url": "https://your-server.com/webhooks/mytv",
"events": ["subscription.created", "subscription.canceled", "payment.failed"],
"secret": "your-webhook-secret",
"isActive": true
}

Lister les Webhooks

GET /api/v2/admin/webhooks
Authorization: Bearer <admin_token>

Supprimer un Webhook

DELETE /api/v2/admin/webhooks/:id
Authorization: Bearer <admin_token>

Format du Payload

Tous les webhooks suivent un format de payload standard :

{
"id": "evt_abc123",
"type": "subscription.created",
"timestamp": "2025-01-15T10:30:00Z",
"apiVersion": "2025-01-01",
"data": {
// Donnees specifiques a l'evenement
}
}

Verification des Webhooks

Les webhooks sont signes avec HMAC-SHA256. Verifiez la signature avant de traiter :

import crypto from 'crypto';

function verifyWebhookSignature(
payload: string,
signature: string,
secret: string,
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature),
);
}

// Middleware Express
app.post(
'/webhooks/mytv',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-mytv-signature'];

if (
!verifyWebhookSignature(req.body.toString(), signature, WEBHOOK_SECRET)
) {
return res.status(401).send('Invalid signature');
}

const event = JSON.parse(req.body);
// Traiter l'evenement...

res.status(200).send('OK');
},
);

Evenements Disponibles

Evenements d'Abonnement

subscription.created

Emis lorsqu'un nouvel abonnement est cree.

{
"type": "subscription.created",
"data": {
"subscriptionId": "sub-uuid",
"accountId": "account-uuid",
"planId": "plan-premium",
"planName": "Premium",
"status": "ACTIVE",
"billingCycle": "MONTHLY",
"amount": 9.99,
"currency": "EUR",
"currentPeriodStart": "2025-01-15T00:00:00Z",
"currentPeriodEnd": "2025-02-15T00:00:00Z"
}
}

subscription.updated

Emis lorsqu'un plan d'abonnement est modifie.

{
"type": "subscription.updated",
"data": {
"subscriptionId": "sub-uuid",
"accountId": "account-uuid",
"previousPlanId": "plan-basic",
"newPlanId": "plan-premium",
"changeType": "UPGRADE",
"effectiveDate": "2025-01-15T00:00:00Z"
}
}

subscription.canceled

Emis lorsqu'un abonnement est annule.

{
"type": "subscription.canceled",
"data": {
"subscriptionId": "sub-uuid",
"accountId": "account-uuid",
"planId": "plan-premium",
"cancelReason": "too_expensive",
"canceledAt": "2025-01-15T10:30:00Z",
"accessEndsAt": "2025-02-15T00:00:00Z"
}
}

subscription.renewed

Emis lorsqu'un abonnement est renouvele avec succes.

{
"type": "subscription.renewed",
"data": {
"subscriptionId": "sub-uuid",
"accountId": "account-uuid",
"planId": "plan-premium",
"amount": 9.99,
"currency": "EUR",
"newPeriodEnd": "2025-03-15T00:00:00Z"
}
}

Evenements de Paiement

payment.succeeded

Emis lorsqu'un paiement reussit.

{
"type": "payment.succeeded",
"data": {
"transactionId": "txn-uuid",
"accountId": "account-uuid",
"subscriptionId": "sub-uuid",
"amount": 9.99,
"currency": "EUR",
"paymentMethod": "CARD",
"last4": "4242",
"invoiceId": "inv-uuid",
"invoiceUrl": "https://..."
}
}

payment.failed

Emis lorsqu'un paiement echoue.

{
"type": "payment.failed",
"data": {
"transactionId": "txn-uuid",
"accountId": "account-uuid",
"subscriptionId": "sub-uuid",
"amount": 9.99,
"currency": "EUR",
"failureReason": "card_declined",
"failureCode": "insufficient_funds",
"nextRetryAt": "2025-01-18T00:00:00Z",
"retryCount": 1,
"maxRetries": 3
}
}

Evenements de Compte

account.created

Emis lorsqu'un nouveau compte est cree.

{
"type": "account.created",
"data": {
"accountId": "account-uuid",
"email": "[email protected]",
"tenantId": "tenant-uuid",
"createdAt": "2025-01-15T10:30:00Z"
}
}

account.deleted

Emis lorsqu'un compte est supprime.

{
"type": "account.deleted",
"data": {
"accountId": "account-uuid",
"email": "u***@example.com",
"deletionType": "USER_REQUEST",
"deletedAt": "2025-01-15T10:30:00Z"
}
}

Evenements de Securite

security.suspicious_activity

Emis lorsqu'une activite suspecte est detectee.

{
"type": "security.suspicious_activity",
"data": {
"accountId": "account-uuid",
"eventType": "IMPOSSIBLE_TRAVEL",
"severity": "HIGH",
"riskScore": 75,
"details": {
"previousLocation": "Paris, France",
"newLocation": "London, UK",
"timeDelta": 1800
},
"actionTaken": "CHALLENGE"
}
}

security.account_locked

Emis lorsqu'un compte est verrouille.

{
"type": "security.account_locked",
"data": {
"accountId": "account-uuid",
"reason": "BRUTE_FORCE",
"failedAttempts": 5,
"lockDuration": 900,
"lockedAt": "2025-01-15T10:30:00Z"
}
}

Evenements de Contenu

content.published

Emis lorsqu'un nouveau contenu est publie.

{
"type": "content.published",
"data": {
"contentId": "movie-uuid",
"contentType": "MOVIE",
"title": "The New Movie",
"accessType": "PREMIUM",
"publishedAt": "2025-01-15T00:00:00Z"
}
}

Politique de Retry

Les livraisons de webhooks echouees sont retentees avec un backoff exponentiel :

TentativeDelai
1Immediat
25 minutes
330 minutes
42 heures
524 heures

Apres 5 tentatives echouees, le webhook est desactive et une notification admin est envoyee.

Exigences de Reponse

Votre endpoint webhook doit :

  1. Repondre dans les 30 secondes
  2. Retourner un code de statut 2xx en cas de succes
  3. Retourner 4xx pour les erreurs client (pas de retry)
  4. Retourner 5xx pour les erreurs serveur (retry automatique)

Headers

Tous les webhooks incluent ces headers :

HeaderDescription
X-MyTV-SignatureSignature HMAC-SHA256
X-MyTV-EventType d'evenement
X-MyTV-TimestampTimestamp Unix
X-MyTV-Delivery-IDID unique de livraison
Content-Typeapplication/json

Idempotence

Les webhooks peuvent etre livres plus d'une fois. Utilisez le champ id pour assurer un traitement idempotent :

async function processWebhook(event: WebhookEvent) {
// Verifier si deja traite
const exists = await db.webhookEvents.findUnique({
where: { eventId: event.id },
});

if (exists) {
console.log(`Event ${event.id} already processed, skipping`);
return;
}

// Traiter l'evenement
await handleEvent(event);

// Marquer comme traite
await db.webhookEvents.create({
data: { eventId: event.id, processedAt: new Date() },
});
}

Test des Webhooks

Endpoint de Test

Envoyez un webhook de test pour verifier votre endpoint :

POST /api/v2/admin/webhooks/:id/test
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"eventType": "subscription.created"
}

Developpement Local

Utilisez ngrok ou des outils similaires pour recevoir les webhooks localement :

ngrok http 3000
# Utilisez l'URL ngrok comme endpoint webhook

Logs des Webhooks

Consultez les logs de livraison des webhooks :

GET /api/v2/admin/webhooks/:id/logs
Authorization: Bearer <admin_token>
{
"data": [
{
"id": "log-uuid",
"eventId": "evt-uuid",
"eventType": "subscription.created",
"status": "DELIVERED",
"statusCode": 200,
"responseTime": 245,
"createdAt": "2025-01-15T10:30:00Z"
},
{
"id": "log-uuid-2",
"eventId": "evt-uuid-2",
"eventType": "payment.failed",
"status": "FAILED",
"statusCode": 500,
"error": "Connection timeout",
"retryAt": "2025-01-15T10:35:00Z",
"createdAt": "2025-01-15T10:30:00Z"
}
]
}

Bonnes Pratiques

  1. Verifiez les signatures - Toujours verifier la signature du webhook
  2. Repondez rapidement - Retournez 200 immediatement, traitez de maniere asynchrone
  3. Gerez les doublons - Implementez un traitement idempotent
  4. Loguez tout - Loguez tous les evenements webhook pour le debug
  5. Surveillez les echecs - Alertez sur les echecs repetes
  6. Utilisez HTTPS - Toujours utiliser des endpoints HTTPS