Cas d’usage
Vérification HMAC-SHA256 des webhooks (timing-safe)
Comparer les hash en temps constant pour éviter le timing attack
Tous les webhooks Scell.io sont signés HMAC-SHA256 avec le secret du webhook (configuré au moment de la création du endpoint). La signature est calculée sur la concaténation `<timestamp>.<raw_body>` (timestamp dans le header `X-Scell-Timestamp`) et envoyée dans `X-Scell-Signature`. Côté receveur, ne JAMAIS comparer les hash avec `===` (vulnérable au timing attack) : utiliser `timingSafeEqual` (Node) ou `hash_equals` (PHP). Vérifier aussi le timestamp : tout message > 5 minutes est rejeté pour empêcher les replay attacks. Sur les webhooks sortants en mTLS optionnel pour les tenants enterprise.
À retenir
- Signature : `HMAC-SHA256(secret, timestamp + '.' + raw_body)`
- Header `X-Scell-Signature` (hex) + `X-Scell-Timestamp` (epoch)
- Tolérance timestamp : 5 minutes (anti-replay)
- Comparaison timing-safe obligatoire (`hash_equals` / `timingSafeEqual`)
- Secret rotativable via dashboard sans downtime
- mTLS optionnel sur sortants pour entreprises
Exemple de code
<?php
// Laravel controller webhook receiver
public function handle(\Illuminate\Http\Request $request)
{
$signature = $request->header('X-Scell-Signature');
$timestamp = (int) $request->header('X-Scell-Timestamp');
$rawBody = $request->getContent();
// 1. Anti-replay
if (abs(time() - $timestamp) > 300) {
abort(401, 'Timestamp too old');
}
// 2. Vérification timing-safe
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, config('services.scell.webhook_secret'));
if (!hash_equals($expected, $signature)) {
abort(401, 'Invalid signature');
}
// 3. Idempotence (table avec UNIQUE sur event_id)
$event = json_decode($rawBody, true);
\App\Models\WebhookEvent::firstOrCreate(['event_id' => $event['event_id']]);
return response()->noContent();
}