Webhooks
Recevez des notifications signées en temps réel à chaque conversion créée, remboursée, payée ou contestée.
Le postback et le SDK envoient des données vers RefCampaign. Les webhooks font l'inverse : RefCampaign envoie un événement JSON signé vers votre serveur dès qu'un événement de cycle de vie d'une conversion se produit — pour synchroniser un CRM, déclencher une automatisation Slack ou Zapier, ou alimenter un pipeline de données sans polling.
Les webhooks sont disponibles à partir du plan Growth.
Activer un webhook
Dans le dashboard, allez dans Réglages → Webhooks → Ajouter un webhook :
- Saisissez l'URL de votre endpoint (obligatoirement en HTTPS).
- Choisissez les événements à recevoir.
- Enregistrez. Le secret de signature (
whsec_…) est affiché une seule fois — conservez-le maintenant.
Vous pouvez configurer jusqu'à 5 webhooks par compte, chacun avec son secret et ses abonnements. Utilisez Envoyer un test pour émettre un ping webhook.test ponctuel et vérifier que votre endpoint répond, et Régénérer le secret pour émettre un nouveau secret de signature.
Événements
| Événement | Émis quand |
|---|---|
conversion.created | Une conversion est enregistrée. |
conversion.refunded | Une conversion est remboursée. |
conversion.commission_paid | Une commission est marquée payée. |
conversion.disputed | Le paiement d'une conversion est contesté. |
conversion.dispute_resolved | Un litige de paiement est résolu. |
Payload
Chaque livraison est un POST avec un corps JSON de cette forme :
{
"event": "conversion.created",
"data": { "conversionId": "conv_abc", "amount": 4990, "currency": "EUR" },
"timestamp": "2026-06-19T12:00:00.000Z",
"idempotencyKey": "conversion.created:conversion:conv_abc"
}| Champ | Type | Notes |
|---|---|---|
event | string | L'un des types d'événements ci-dessus. |
data | object | Payload propre à l'événement. |
timestamp | string | Heure d'émission ISO 8601. |
idempotencyKey | string | Stable par (événement, ressource). Sert à dédupliquer — le même événement porte toujours la même clé, y compris lors des retries. |
Vérifier la signature
Chaque requête porte un header X-RefCampaign-Signature, avec le même schéma que Stripe :
X-RefCampaign-Signature: t=<unix_seconds>,v1=<hex_sha256>La signature est HMAC-SHA256(secret, "<t>.<rawBody>"). Vérifiez-la toujours sur le corps brut de la requête, avant de parser le JSON — un corps re-sérialisé ne correspondra pas.
Vérifiez avant de faire confiance
Un webhook non vérifié peut être falsifié par quiconque connaît votre URL. Rejetez toute requête dont la signature n'est pas valide, et toute requête dont le timestamp sort de la fenêtre de tolérance (5 minutes par défaut) pour bloquer les rejeux.
Sur les stacks JS/TS, le SDK fournit un helper :
import { verifyWebhookSignature, WEBHOOK_SIGNATURE_HEADER } from '@refcampaign/sdk'
// Express — express.raw() pour garder le corps non parsé
app.post('/webhooks/refcampaign', express.raw({ type: 'application/json' }), (req, res) => {
const ok = verifyWebhookSignature({
secret: process.env.REFCAMPAIGN_WEBHOOK_SECRET,
payload: req.body.toString('utf8'),
header: req.header(WEBHOOK_SIGNATURE_HEADER) ?? '',
})
if (!ok) return res.status(400).send('signature invalide')
const event = JSON.parse(req.body.toString('utf8'))
switch (event.event) {
case 'conversion.created':
// …
break
}
res.sendStatus(200)
})Sur les autres stacks, recalculez le HMAC vous-même :
import hmac, hashlib, time
def verify(secret: str, raw_body: str, header: str, tolerance: int = 300) -> bool:
parts = dict(p.split('=', 1) for p in header.split(','))
ts, sig = int(parts['t']), parts['v1']
if abs(time.time() - ts) > tolerance:
return False
expected = hmac.new(secret.encode(), f'{ts}.{raw_body}'.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)Retries et idempotence
- Répondez avec un statut 2xx dès que vous avez accepté l'événement. Tout statut non-2xx (ou un timeout au-delà de 10 s) est considéré comme un échec.
- Les livraisons en échec sont réessayées avec un backoff exponentiel (jusqu'à 5 tentatives).
- Les livraisons sont idempotentes sur
idempotencyKey— un retry ne produit jamais de seconde livraison réussie, et votre handler doit traiter une clé répétée comme un no-op. - Gardez votre handler rapide : faites le travail lourd en asynchrone et renvoyez 2xx immédiatement.
Chaque livraison (et son statut de réponse) est enregistrée dans Réglages → Webhooks → Voir les livraisons pour l'audit et le débogage.
Sécurité
- Exposez votre endpoint uniquement en HTTPS.
- Stockez le secret de signature dans un gestionnaire de secrets — jamais dans le code source.
- Vérifiez la signature à chaque requête, sur le corps brut, avec une comparaison à temps constant.
- Régénérez le secret en cas de fuite — l'ancien secret cesse de valider immédiatement.