300) { http_response_code(401); exit; } // 3. X-ConsensoSicuro-Signature: uno o due valori «v1=» separati da // virgola (due per 24 ore dopo una rotazione del segreto). Ognuno è // l'HMAC-SHA256 di «timestamp.corpo». Basta che UNO torni. hash_equals // confronta a tempo costante. $expected = hash_hmac('sha256', $timestamp . '.' . $raw, $secret); $valid = false; foreach (explode(',', $signatures) as $part) { $pair = explode('=', trim($part), 2); if (count($pair) === 2 && $pair[0] === 'v1' && preg_match('/^[0-9a-f]{64}$/', strtolower($pair[1])) && hash_equals($expected, strtolower($pair[1]))) { $valid = true; } } if (!$valid) { http_response_code(401); exit; } $event = json_decode($raw, true); // 4. Protezione dal replay, oltre al timestamp: deduplica su // X-ConsensoSicuro-Delivery-Id, che resta uguale fra un tentativo e le sue // riprove. Ricorda gli id elaborati per almeno 30 giorni (il «Riprova» // della dashboard vale 30 giorni); qui un file come esempio, nel gestionale // una tabella con indice unico. $deliveryId = $_SERVER['HTTP_X_CONSENSOSICURO_DELIVERY_ID'] ?? ''; $seenFile = sys_get_temp_dir() . '/consensosicuro-consegne.txt'; $seen = is_file($seenFile) ? file($seenFile, FILE_IGNORE_NEW_LINES) : []; if ($deliveryId === '' || in_array($deliveryId, $seen, true)) { http_response_code(200); // già elaborata: rispondi 2xx, così non viene riprovata exit; } file_put_contents($seenFile, $deliveryId . PHP_EOL, FILE_APPEND | LOCK_EX); // 5. Gestisci gli eventi che ti servono. Tieni il lavoro lungo fuori da qui: // se non rispondi 2xx entro 10 secondi la consegna viene riprovata. switch ($event['event'] ?? '') { case 'consent.completed': // Tutte le firme ricevute: scarica il PDF con GET /consents/{id}/pdf. $consentId = $event['consent']['id']; break; case 'consent.partially_signed': // Doppia firma: ha firmato un solo genitore ($event['consent']['signedSlot']). break; case 'consent.action_required': // Dati del minore diversi o controfirma mancante: serve un tuo intervento. break; case 'consent.delivery_failed': // Il messaggio non è arrivato: correggi il numero con POST /consents/{id}/correct-phone. break; case 'patient.deleted': // Cancellazione GDPR eseguita: arrivano solo gli identificativi. break; case 'ping': // Evento di prova. break; } http_response_code(200);