config->getApiKey(); if (!$this->config->isEnabled() || $apiKey === '') { throw new \RuntimeException('17TRACK is disabled or the API key is missing.'); } if ($rawBody === '' || strlen($rawBody) > self::MAX_BODY_BYTES) { throw new InputException(__('The 17TRACK webhook body is empty or too large.')); } $this->verifySignature($rawBody, $signature, $apiKey); try { $payload = $this->json->unserialize($rawBody); } catch (\InvalidArgumentException $exception) { throw new InputException(__('The 17TRACK webhook body is not valid JSON.'), $exception); } if (!is_array($payload) || !is_array($payload['data'] ?? null)) { throw new InputException(__('The 17TRACK webhook payload is missing data.')); } $event = (string)($payload['event'] ?? ''); $data = $payload['data']; $number = trim((string)($data['number'] ?? '')); if ($number === '') { throw new InputException(__('The 17TRACK webhook payload is missing a tracking number.')); } if ($event === 'TRACKING_UPDATED') { $orderIds = $this->tracking->saveTrackingInfo( $number, $data, $this->json->serialize($data) ); if ($orderIds === []) { $this->logger->warning('Ignored a 17TRACK webhook for an unknown tracking number.', [ 'tracking_number' => $number, ]); return [ 'event' => $event, 'tracking_number' => $number, 'status' => 'ignored', 'message' => 'Unknown tracking number.', ]; } $this->deliveryStatusUpdater->execute($orderIds); return [ 'event' => $event, 'tracking_number' => $number, 'status' => 'success', 'message' => 'Tracking update processed.', ]; } if ($event === 'TRACKING_STOPPED') { $this->tracking->saveStoppedInfo($number, $this->json->serialize($data)); return [ 'event' => $event, 'tracking_number' => $number, 'status' => 'success', 'message' => 'Tracking stopped event processed.', ]; } $this->logger->info('Ignored an unsupported 17TRACK webhook event.', [ 'event' => $event, 'tracking_number' => $number, ]); return [ 'event' => $event, 'tracking_number' => $number, 'status' => 'ignored', 'message' => 'Unsupported webhook event.', ]; } private function verifySignature(string $rawBody, string $signature, string $apiKey): void { $signature = strtolower(trim($signature)); if (!preg_match('/^[a-f0-9]{64}$/', $signature)) { throw new AuthenticationException(__('The 17TRACK webhook signature is missing or invalid.')); } $expected = hash('sha256', $rawBody . '/' . $apiKey); if (!hash_equals($expected, $signature)) { throw new AuthenticationException(__('The 17TRACK webhook signature is missing or invalid.')); } } }