."), sapma 5 dakika. $secret = getenv('IBRESE_WEBHOOK_SECRET'); $rpc = rtrim(getenv('IBRESE_RPC') ?: 'https://rpc.ibrese.com', '/'); $raw = file_get_contents('php://input'); $header = $_SERVER['HTTP_X_IBRESE_WEBHOOK_SIGNATURE'] ?? ''; $eventId = $_SERVER['HTTP_X_IBRESE_WEBHOOK_ID'] ?? ''; function parse_sig(string $header): array { $map = []; foreach (explode(',', $header) as $part) { $i = strpos($part, '='); if ($i > 0) { $map[trim(substr($part, 0, $i))] = trim(substr($part, $i + 1)); } } $t = intval($map['t'] ?? 0); $v1 = $map['v1'] ?? ''; if ($t <= 0 || !preg_match('/^[0-9a-f]{64}$/', $v1)) { throw new RuntimeException('geçersiz imza başlığı'); } return [$t, $v1]; } header('Content-Type: application/json; charset=utf-8'); try { [$t, $v1] = parse_sig($header); } catch (Throwable $e) { http_response_code(401); echo json_encode(['error' => 'bad signature']); exit; } if (abs((int) round(microtime(true) * 1000) - $t) > 5 * 60 * 1000) { http_response_code(401); echo json_encode(['error' => 'skew']); exit; } $expected = hash_hmac('sha256', $t . '.' . $raw, $secret); if (!hash_equals($expected, $v1)) { http_response_code(401); echo json_encode(['error' => 'bad signature']); exit; } $event = json_decode($raw, true); if (($event['type'] ?? '') === 'payment.finalized' && !empty($event['invoicePaid']) && !empty($event['paymentId'])) { $ctx = stream_context_create(['http' => ['method' => 'GET', 'timeout' => 8]]); $obs = @file_get_contents($rpc . '/payments/' . rawurlencode($event['paymentId']), false, $ctx); $body = $obs ? json_decode($obs, true) : null; $paid = $body && ($body['status'] ?? '') === 'FINALIZED' && in_array($body['amountRelation'] ?? '', ['MATCH', 'OVERPAYMENT'], true); if ($paid) { // TODO: siparişi ($event['merchantReference']) ödendi işaretleyin. $eventId ile idempotent olun. error_log('IBRESE paid ' . ($event['merchantReference'] ?? '') . ' ' . $event['paymentId']); } } http_response_code(200); echo json_encode(['ok' => true, 'id' => $eventId]);