Webhooks
Recevez des notifications en temps réel pour tous les événements de votre compte. Factures, signatures, alertes de solde — restez informé instantanément.
Démarrage rapide
Configurez vos webhooks depuis le tableau de bord ou via l'API pour commencer à recevoir des notifications en temps réel.
Configurer l'URL webhook
Ajoutez votre URL de callback dans le tableau de bord sous Paramètres > Webhooks. Cette URL doit être en HTTPS et accessible publiquement.
Récupérer votre secret
Copiez votre secret webhook (whsec_xxx...). Conservez-le de manière sécurisée — il vous servira à vérifier les webhooks entrants.
Vérifier les signatures
Vérifiez toujours l'en-tête X-Scell-Signature avant de traiter les événements. Cela garantit l'authenticité.
Vérification de signature
Vérifiez toujours les signatures des webhooks pour vous assurer que les requêtes proviennent de Scell.io. La signature est incluse dans l'en-tête X-Scell-Signature.
t=timestamp,v1=signature. Utilisez toujours des fonctions de comparaison résistantes aux attaques temporelles pour prévenir les timing attacks.<?phpnamespace App\Http\Controllers;use Illuminate\Http\Request;use Illuminate\Http\Response;class WebhookController extends Controller{ private string $webhookSecret; public function __construct() { $this->webhookSecret = config('services.scell.webhook_secret'); } public function handle(Request $request): Response { // 1. Get signature header $signatureHeader = $request->header('X-Scell-Signature'); if (!$signatureHeader) { return response('Missing signature header', 400); } // 2. Parse timestamp and signature $elements = $this->parseSignatureHeader($signatureHeader); $timestamp = $elements['timestamp']; $signature = $elements['signature']; // 3. Verify timestamp (replay attack protection) if (abs(time() - $timestamp) > 300) { return response('Timestamp expired', 403); } // 4. Verify signature $payload = $request->getContent(); $expectedSignature = $this->computeSignature($timestamp, $payload); if (!hash_equals($expectedSignature, $signature)) { return response('Invalid signature', 403); } // 5. Process event $event = $request->input('event'); $data = $request->input('data'); $this->processEvent($event, $data); return response('OK', 200); } private function parseSignatureHeader(string $header): array { $parts = explode(',', $header); $elements = []; foreach ($parts as $part) { [$key, $value] = explode('=', $part, 2); $elements[$key] = $value; } return [ 'timestamp' => (int) $elements['t'], 'signature' => $elements['v1'], ]; } private function computeSignature(int $timestamp, string $payload): string { $signedPayload = "{$timestamp}.{$payload}"; return hash_hmac('sha256', $signedPayload, $this->webhookSecret); } private function processEvent(string $event, array $data): void { match ($event) { 'invoice.validated' => $this->handleInvoiceValidated($data), 'signature.completed' => $this->handleSignatureCompleted($data), 'balance.low' => $this->handleBalanceLow($data), default => null, }; }}Référence des événements
Abonnez-vous aux événements dont vous avez besoin. Chaque événement inclut les données pertinentes dans son payload.
| Event | Description |
|---|---|
invoice.created | Facture créée |
invoice.validated | Facture validée (Factur-X/UBL généré) |
invoice.submitted | Facture soumise au PDP |
invoice.accepted | Facture acceptée par le destinataire |
invoice.rejected | Facture rejetée |
invoice.paid | Facture marquée comme payée |
| Event | Description |
|---|---|
signature.created | Demande de signature créée |
signature.signed | Document signé par un signataire |
signature.completed | Tous les signataires ont signé |
signature.declined | Signature refusée |
signature.expired | Demande de signature expirée |
| Event | Description |
|---|---|
balance.low | Solde de crédits faible (seuil d'alerte) |
balance.depleted | Solde de crédits épuisé |
kyb.approved | Vérification KYB approuvée |
kyb.rejected | Vérification KYB rejetée |
Structure du payload
Tous les webhooks sont envoyés en requête POST avec un corps JSON. Voici un exemple de payload :
{ "event": "invoice.validated", "webhook_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2024-01-15T10:30:00+00:00", "company_id": "123e4567-e89b-12d3-a456-426614174000", "data": { "invoice": { "id": "inv_abc123", "invoice_number": "FAC-2024-001", "status": "validated", "total_amount": 1200.00, "currency": "EUR", "customer": { "name": "Client SARL", "siren": "123456789" }, "created_at": "2024-01-15T09:00:00+00:00", "validated_at": "2024-01-15T10:30:00+00:00" } }}Champs communs
| Champ | Type | Description |
|---|---|---|
event | string | Type d'événement (ex. invoice.validated) |
webhook_id | uuid | Identifiant unique de la configuration webhook |
timestamp | ISO 8601 | Horodatage de l'événement |
company_id | uuid | Identifiant de la société associée à l'événement |
data | object | Données spécifiques à l'événement |
Bonnes pratiques
Suivez ces recommandations pour garantir un traitement fiable des webhooks.
Toujours vérifier les signatures
Ne traitez jamais un webhook sans vérifier sa signature au préalable. Cela évite les requêtes forgées.
Répondre rapidement
Retournez un code 200 en moins de 5 secondes. Traitez la logique lourde de manière asynchrone via une file d'attente.
Implémenter l'idempotence
Utilisez webhook_id et timestamp pour détecter les doublons. Les webhooks peuvent être retentés en cas d'échec.
Gérer les nouvelles tentatives
Scell.io retente les webhooks échoués avec un backoff exponentiel. Concevez votre gestionnaire pour être résilient.
Politique de retry
Scell.io retente automatiquement les livraisons de webhooks échouées avec un backoff exponentiel.
| Tentative | Délai | Temps total |
|---|---|---|
| 1re tentative | 1 minute | 1 min |
| 2e tentative | 5 minutes | 6 min |
| 3e tentative | 30 minutes | 36 min |
Prêt à intégrer les webhooks ?
Créez votre compte et configurez vos webhooks en quelques minutes.