Documentation/Postbacks S2S

Postbacks serveur-à-serveur

Les postbacks permettent aux affiliés de recevoir en temps réel les conversions confirmées et remboursées. Chaque postback est signé HMAC-SHA256 et inclut un mécanisme anti-replay.

Fonctionnement

Lorsqu'une conversion est confirmée (paiement réussi) ou remboursée, Flendra envoie un POST HTTP à l'URL de postback configurée par l'affilié. Le payload est signé avec HMAC-SHA256 pour garantir l'intégrité et l'authenticité.

Configurer un endpoint de postback

L'affilié configure son URL de réception et un secret partagé :

bash
curl -X POST https://flendra.com/api/postback-endpoints \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -d '{
    "url": "https://votre-site.com/webhook/flendra",
    "secret": "whsec_votre_secret_partage_aleatoire",
    "scope": "full",
    "events": ["confirmed", "refunded"],
    "active": true
  }'

Réponse :

json
{
  "success": true,
  "data": {
    "id": "pbe_abc123",
    "affiliateUserId": "usr_aff456",
    "url": "https://votre-site.com/webhook/flendra",
    "scope": "full",
    "events": ["confirmed", "refunded"],
    "active": true,
    "maxRetries": 3,
    "retryDelay": 60,
    "deadLetterAt": null,
    "deadLetterReason": null,
    "lastSentAt": null,
    "lastStatus": null,
    "createdAt": "2026-06-17T10:30:00.000Z",
    "updatedAt": "2026-06-17T10:30:00.000Z"
  }
}

Sécurité : Le secret n'est jamais retourné par l'API après la création. Stockez-le de votre côté. Utilisez un secret d'au moins 32 caractères généré aléatoirement.

Format du payload

Chaque postback contient les champs suivants :

Scope minimal (défaut)

json
{
  "version": "1",
  "postbackId": "pbk_uuid_v4_unique",
  "conversionId": "conv_def456",
  "affiliateUserId": "usr_aff456",
  "offerId": "off_abc123",
  "commissionAmount": 1500,
  "currency": "EUR",
  "status": "confirmed",
  "timestamp": "2026-06-17T10:35:00.000Z",
  "nonce": "nonce_uuid_v4_anti_replay",
  "signature": "hmac_sha256_hex_signature"
}

Scope complet

Le scope full ajoute les champs supplémentaires :

json
{
  "version": "1",
  "postbackId": "pbk_uuid_v4_unique",
  "conversionId": "conv_def456",
  "affiliateUserId": "usr_aff456",
  "offerId": "off_abc123",
  "commissionAmount": 1500,
  "currency": "EUR",
  "status": "confirmed",
  "timestamp": "2026-06-17T10:35:00.000Z",
  "nonce": "nonce_uuid_v4_anti_replay",
  "clickId": "clk_abc123",
  "linkId": "link_xyz789",
  "commissionRuleId": "rule_pct30",
  "commissionType": "percentage",
  "orderAmount": 4999,
  "sub1": "newsletter",
  "sub2": "janvier-2026",
  "sub3": null,
  "sub4": null,
  "sub5": null,
  "explanation": "Last-click attribution within 30-day window",
  "windowStart": "2026-05-18T10:35:00.000Z",
  "windowEnd": "2026-06-17T10:35:00.000Z",
  "signature": "hmac_sha256_hex_signature"
}

Vérification de la signature

La signature HMAC-SHA256 est calculée sur le payload sérialisé (sans le champ signature) en utilisant le secret partagé. Voici un exemple en Python :

python
import hmac
import hashlib
import json

def verify_postback(payload: dict, secret: str, signature: str) -> bool:
    """Verify HMAC-SHA256 signature of a Flendra postback."""
    # Remove the signature from the payload before verification
    payload_to_sign = {k: v for k, v in payload.items() if k != "signature"}
    # Serialize with sorted keys for deterministic output
    message = json.dumps(payload_to_sign, sort_keys=True, separators=(",", ":"))
    expected = hmac.new(
        secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# Usage
payload = request.get_json()
if verify_postback(payload, "whsec_votre_secret", payload["signature"]):
    process_conversion(payload)
else:
    return "Invalid signature", 401

Et en Node.js :

javascript
const crypto = require('crypto');

function verifyPostback(payload, secret, signature) {
  const { signature: _, ...rest } = payload;
  const message = JSON.stringify(rest, Object.keys(rest).sort());
  const expected = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(signature, 'hex')
  );
}

Protection anti-replay

Chaque postback inclut un nonce unique (UUID v4) et un timestamp. Pour vous protéger contre les rejeux :

  1. Vérifiez que le timestamp est dans une fenêtre acceptable (±5 minutes)
  2. Stockez les nonces déjà traités et rejetez les doublons
  3. Vérifiez la signature HMAC avant tout traitement

Politique de retry

En cas d'échec (timeout, erreur HTTP), Flendra retente avec backoff exponentiel :

  • maxRetries : 3 (défaut)
  • retryDelay : 60 secondes (défaut)
  • Backoff : delay × 2^(attempt-1) + jitter(0-1000ms)

Après épuisement des retries, l'endpoint est marqué dead-letter et doit être réactivé manuellement via POST /postback-endpoints/:id/reactivate.

Types d'événements

ÉvénementDescriptionStatus
confirmedConversion confirmée (paiement réussi)confirmed
refundedCommande remboursée (total ou partiel)refunded
pendingConversion en attente de confirmationpending
disputedTransaction contestée (chargeback)disputed

Gestion des endpoints

MéthodeEndpointDescription
POST/postback-endpointsCréer un endpoint
GET/postback-endpoints/meLister ses endpoints
GET/postback-endpoints/:idObtenir un endpoint
PUT/postback-endpoints/:idMettre à jour
DELETE/postback-endpoints/:idSupprimer
POST/postback-endpoints/:id/reactivateRéactiver (dead-letter)

API publique

Consultez la référence complète des endpoints API.

Lire la suite →