Notifications en temps réel

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.

1

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.

2

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.

3

Vérifier les signatures

Vérifiez toujours l'en-tête X-Scell-Signature avant de traiter les événements. Cela garantit l'authenticité.

Bonne pratique de sécurité : N'exposez jamais votre secret webhook dans du code côté client. Vérifiez toujours les signatures sur votre serveur backend.

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.

Important : Le format de signature est t=timestamp,v1=signature. Utilisez toujours des fonctions de comparaison résistantes aux attaques temporelles pour prévenir les timing attacks.
WebhookController.php
php
<?php
namespace 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.

Événements factures
EventDescription
invoice.createdFacture créée
invoice.validatedFacture validée (Factur-X/UBL généré)
invoice.submittedFacture soumise au PDP
invoice.acceptedFacture acceptée par le destinataire
invoice.rejectedFacture rejetée
invoice.paidFacture marquée comme payée
Événements signatures
EventDescription
signature.createdDemande de signature créée
signature.signedDocument signé par un signataire
signature.completedTous les signataires ont signé
signature.declinedSignature refusée
signature.expiredDemande de signature expirée
Événements compte
EventDescription
balance.lowSolde de crédits faible (seuil d'alerte)
balance.depletedSolde de crédits épuisé
kyb.approvedVérification KYB approuvée
kyb.rejectedVé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 :

invoice.validated payload
json
{
"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

ChampTypeDescription
eventstringType d'événement (ex. invoice.validated)
webhook_iduuidIdentifiant unique de la configuration webhook
timestampISO 8601Horodatage de l'événement
company_iduuidIdentifiant de la société associée à l'événement
dataobjectDonné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.

TentativeDélaiTemps total
1re tentative1 minute1 min
2e tentative5 minutes6 min
3e tentative30 minutes36 min
Codes de succès : Toute réponse 2xx est considérée comme un succès. Le webhook est marqué comme livré.
Gestion des échecs : Après 3 échecs consécutifs, l'endpoint webhook est automatiquement désactivé. Réactivez-le depuis votre tableau de bord.
Timeout : Les requêtes expirent après 30 secondes. Répondez rapidement et traitez de manière asynchrone.

Prêt à intégrer les webhooks ?

Créez votre compte et configurez vos webhooks en quelques minutes.