From cc4e59a3e5b512169634fdba228219ab24687ad2 Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Wed, 4 Mar 2026 11:35:49 +0100 Subject: [PATCH] Fix inbound caller detection for Bird voice webhooks --- .../app/routes/birdVoiceWebhooksRoute.php | 99 ++++++++++++++++++- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/services/nginx/app/routes/birdVoiceWebhooksRoute.php b/services/nginx/app/routes/birdVoiceWebhooksRoute.php index 6460bc63..aea52aba 100644 --- a/services/nginx/app/routes/birdVoiceWebhooksRoute.php +++ b/services/nginx/app/routes/birdVoiceWebhooksRoute.php @@ -130,6 +130,16 @@ class birdVoiceWebhooksRoute $payload['caller'] ?? null, $payload['source'] ?? null, $payload['originator'] ?? null, + $payload['call']['from'] ?? null, + $payload['call']['caller'] ?? null, + $payload['data']['from'] ?? null, + $payload['data']['caller'] ?? null, + $payload['payload']['from'] ?? null, + $payload['payload']['caller'] ?? null, + $payload['message']['from'] ?? null, + $payload['message']['caller'] ?? null, + $payload['callEvent']['from'] ?? null, + $payload['callEvent']['caller'] ?? null, ]; foreach ($candidates as $candidate) { @@ -139,14 +149,66 @@ class birdVoiceWebhooksRoute } } + $recursiveCandidates = $this->extractRecursiveCallerCandidates($payload); + foreach ($recursiveCandidates as $candidate) { + $normalized = $this->normalizePhoneCandidate($candidate); + if ($normalized !== null) { + return $normalized; + } + } + return null; } + private function extractRecursiveCallerCandidates(array $payload): array + { + $candidates = []; + $priorityKeys = [ + 'from', 'caller', 'source', 'originator', 'from_number', 'fromnumber', 'caller_id', 'callerid', 'msisdn' + ]; + $this->collectCandidates($payload, $priorityKeys, $candidates); + return $candidates; + } + + private function collectCandidates(array $node, array $priorityKeys, array &$candidates): void + { + foreach ($node as $key => $value) { + $normalizedKey = strtolower((string)$key); + if (in_array($normalizedKey, $priorityKeys, true)) { + $candidates[] = $value; + } + + if (is_array($value)) { + if ( + array_key_exists('phone_number', $value) + || array_key_exists('phone', $value) + || array_key_exists('number', $value) + || array_key_exists('msisdn', $value) + || array_key_exists('e164', $value) + ) { + $candidates[] = $value; + } + $this->collectCandidates($value, $priorityKeys, $candidates); + } + } + } + private function normalizePhoneCandidate(mixed $candidate): ?array { if (is_array($candidate)) { $country = isset($candidate['country_code']) ? (int)$candidate['country_code'] : null; - $phone = $candidate['phone_number'] ?? $candidate['phone'] ?? $candidate['number'] ?? null; + if ($country === null && isset($candidate['countryCode'])) { + $country = (int)$candidate['countryCode']; + } + + $phone = $candidate['phone_number'] + ?? $candidate['phone'] + ?? $candidate['number'] + ?? $candidate['msisdn'] + ?? $candidate['e164'] + ?? $candidate['from'] + ?? null; + if ($phone !== null) { return $this->normalizePhone((string)$phone, $country); } @@ -162,10 +224,11 @@ class birdVoiceWebhooksRoute private function normalizePhone(string $raw, ?int $defaultCountryCode = 45): ?array { - $trimmed = trim($raw); + $trimmed = trim(str_ireplace('tel:', '', $raw)); if ($trimmed === '') { return null; } + $digits = preg_replace('/\D+/', '', $trimmed); if (!is_string($digits) || $digits === '') { return null; @@ -175,8 +238,20 @@ class birdVoiceWebhooksRoute $phone = $digits; if (str_starts_with($trimmed, '+') && strlen($digits) > 8) { - $country = (int)substr($digits, 0, 2); - $phone = substr($digits, 2); + $candidateCodes = [1, 45, 46, 47, 31, 44, 49]; + $matchedCode = null; + foreach ($candidateCodes as $candidateCode) { + $codeString = (string)$candidateCode; + if (str_starts_with($digits, $codeString) && strlen($digits) > strlen($codeString) + 4) { + $matchedCode = $candidateCode; + break; + } + } + + if ($matchedCode !== null) { + $country = $matchedCode; + $phone = substr($digits, strlen((string)$matchedCode)); + } } if ($country === null || $country <= 0 || $phone === '') { @@ -196,7 +271,19 @@ class birdVoiceWebhooksRoute return true; } - return (new subusers_o())->getSubuserByPhone($countryCode, $phone) !== null; + $defaultCountryRows = (new users_o())->getFieldsWhere([ + 'phone_country_code' => 45, + 'phone' => $phone, + ], ['id']); + if (!empty($defaultCountryRows)) { + return true; + } + + if ((new subusers_o())->getSubuserByPhone($countryCode, $phone) !== null) { + return true; + } + + return (new subusers_o())->getSubuserByPhone(45, $phone) !== null; } private function extractDepartmentId(array $payload, array $departmentOptions): ?int @@ -229,6 +316,8 @@ class birdVoiceWebhooksRoute $payload['digits'] ?? null, $payload['dtmf'] ?? null, $payload['digit'] ?? null, + $payload['data']['digits'] ?? null, + $payload['call']['digits'] ?? null, ]; foreach ($digitCandidates as $candidate) {