Dynamiczne numery z API
Twój serwer decyduje, na jakie numery dzwoni kolejka zewnętrzna — osobno dla każdego oczekującego rozmówcy. Kontrakt, podpis, limity, tryb awaryjny i gotowe przykłady.
Zamiast stałej listy członków kolejka zewnętrzna może pytać Twój serwer, na jakie numery dzwonić. Gdy dzwoniący wchodzi do kolejki, Mediflo wysyła podpisane zapytanie z jego numerem, a Ty odpowiadasz listą numerów — na przykład jego stałego opiekuna, osoby z dzisiejszego dyżuru albo działu, który go obsługuje.
Tabele i przykłady na tej stronie są generowane z tego samego modułu, którym Mediflo buduje zapytanie i sprawdza odpowiedź. Wersja maszynowa, razem ze schematami JSON zapytania i odpowiedzi: /api/queue-routing.json.
Jak to działa
- Dzwoniący wchodzi do kolejki zewnętrznej ze źródłem numerów „Dynamicznie z API”.
- Mediflo wysyła jedno podpisane zapytanie
POSTna adres ustawiony w kolejce. Jedno zapytanie na jedno wejście do kolejki — nie na każde połączenie i nie co kilka sekund. - Twój serwer odpowiada listą numerów. Kolejność na liście to kolejność wybierania.
- Mediflo sprawdza każdy numer (walidacja) i dzwoni po kolei na poprawne — na co najwyżej 10.
- Odbierający słyszy krótki komunikat z nazwą kolejki i numerem dzwoniącego: „wciśnij 1, aby przyjąć, 2, aby odrzucić”. Rozmowę przejmuje pierwsza osoba, która wciśnie 1.
- Gdy lista się skończy, a nikt nie przyjął, Mediflo po kilku sekundach zaczyna ją od początku — tę samą listę, bez ponownego pytania Twojego serwera (jeśli w całym przejściu żaden numer nawet nie zadzwonił, przerwa trwa 30 sekund — zob. Co się dzieje w trakcie oczekiwania). Tak aż do skutku: ktoś przyjmie, dzwoniący się rozłączy albo przepływ IVR wyprowadzi go z kolejki (np. po upływie czasu oczekiwania).
Jeśli Twój serwer nie odpowie poprawnie, kolejka przechodzi w tryb awaryjny wybrany w ustawieniach — dzwoniący nigdy nie zostaje bez obsługi tylko dlatego, że Twoje API nie działa.
Konfiguracja w Portalu
Funkcja na zamówienie
Numery z API włączamy dla organizacji na prośbę. Jeśli nie widzisz opcji „Dynamicznie z API”, skontaktuj się z Mediflo.
Konfigurację zmienia użytkownik z uprawnieniem do zarządzania kolejkami. Każda zmiana ustawień i każda rotacja sekretu trafia do dziennika audytu.
Otwórz ustawienia kolejki
W Portalu wejdź w Kolejki, wybierz kolejkę typu Zewnętrzna i przejdź do jej ustawień.
Zmień źródło numerów
W polu Źródło numerów wybierz Dynamicznie z API.
Podaj adres URL
Wpisz adres swojego endpointu. Wymagania: https://, adres publicznie osiągalny z internetu, bez loginu i hasła w adresie. Przekierowań (3xx) nie obsługujemy — podaj adres końcowy.
Wybierz tryb awaryjny
W polu Gdy API nie odpowie lub zwróci błąd zdecyduj, co się stanie, gdy Twój serwer nie odpowie poprawnie: Dzwoń do członków kolejki (według ich harmonogramów) albo Wyjdź z kolejki (przepływ IVR wyprowadza dzwoniącego z kolejki). Szczegóły w sekcji Tryb awaryjny. W sekcji Zaawansowane możesz też zmienić limit czasu odpowiedzi (domyślnie 3 s).
Zapisz i skopiuj sekret podpisu
Kliknij Zapisz. Po pierwszym zapisaniu Mediflo wygeneruje sekret podpisu w postaci whsec_… — skopiuj go (Pokaż, Kopiuj) do konfiguracji swojego serwera; posłuży do weryfikacji podpisu. To osobny sekret tej kolejki, nie sekret webhooków. Przycisk Wygeneruj nowy tworzy nowy sekret, a poprzedni podpisuje zapytania jeszcze przez 24 godziny.
Wyślij testowe zapytanie
Kliknij Wyślij testowe zapytanie. Portal pokaże status HTTP, czas odpowiedzi i każdy zwrócony numer z wynikiem walidacji. Nikt nie zostanie wybrany. W sekcji Ostatnie zapytania widać 20 ostatnich zapytań — testowych i prawdziwych.
Wyjście z kolejki wymaga gałęzi w przepływie IVR
Jeśli wybierasz tryb wyjście z kolejki, ustaw w węźle Kolejka przepływu IVR gałąź Przepełnienie albo Timeout. To nią dzwoniący opuści kolejkę, gdy Twój serwer nie odpowie.
Zapytanie
| Nagłówek | Znaczenie |
|---|---|
| Metoda | POST na adres URL ustawiony w kolejce (tylko https://). |
| Content-Type | application/json |
| Mediflo-Signature | Podpis HMAC-SHA256: t=<unix>,v1=<hex>. W okresie rotacji sekretu dwa wpisy v1. |
| Mediflo-Event-Id | Identyfikator zapytania (evt_…), taki sam jak pole id. |
| Mediflo-Event-Type | Zawsze queue.numbers.requested. |
| Mediflo-Event-Test | true tylko dla zapytania testowego z Portalu. Nikt nie czeka wtedy na połączenie. |
| User-Agent | Mediflo-Queue-Routing/1 |
Przykładowe ciało:
{ "id": "evt_7f1c2d4e-8a9b-4c3d-9e0f-1a2b3c4d5e6f", "type": "queue.numbers.requested", "apiVersion": "v1", "occurredAt": "2026-10-01T08:15:02.120Z", "data": { "queue": { "id": "3b0f6c2e-5a4d-4f1e-9c8b-7a6d5e4f3c2b", "name": "Rejestracja" }, "call": { "id": "9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a", "calledNumber": "+48221234567", "enteredQueueAt": "2026-10-01T08:15:01.950Z" }, "caller": { "phoneNumber": "+48600111222" } }}| Pole | Typ | Znaczenie |
|---|---|---|
id | string | Identyfikator zapytania (evt_<uuid>). Ta sama wartość jest w nagłówku Mediflo-Event-Id. |
type | string | Zawsze queue.numbers.requested. |
apiVersion | string | Wersja kontraktu, obecnie v1. |
occurredAt | string (ISO 8601, UTC) | Moment utworzenia zapytania. |
data.queue.id | string (UUID) | Identyfikator kolejki w Mediflo. |
data.queue.name | string | Nazwa kolejki z Portalu. |
data.call.id | string (UUID) | Identyfikator rozmowy w Mediflo. |
data.call.calledNumber | string | null | Numer, pod który zadzwoniono (E.164), albo null, gdy nie jest znany. |
data.call.enteredQueueAt | string (ISO 8601, UTC) | Moment wejścia dzwoniącego do kolejki. |
data.caller.phoneNumber | string | null | Numer dzwoniącego (E.164) albo null, gdy numer jest zastrzeżony. |
O dzwoniącym wysyłamy wyłącznie numer telefonu. Zapytanie nie zawiera imienia i nazwiska, identyfikatora kontaktu ani żadnych innych jego danych osobowych. Jeśli potrzebujesz więcej, wyszukaj numer we własnym systemie.
Pole id (i nagłówek Mediflo-Event-Id) ma tę samą wartość także przy ponowieniu — możesz po nim rozpoznać, że to samo zapytanie przyszło drugi raz. Parsuj pobłażliwie: ignoruj pola, których nie znasz.
Weryfikacja podpisu
Podpis działa dokładnie tak samo jak w webhookach — jeśli masz już odbiornik webhooków Mediflo, użyj tej samej funkcji, tylko z sekretem kolejki. Pełny opis: Weryfikacja podpisu webhooków.
Mediflo-Signature: t=<sekundy-unix>,v1=<hex>[,v1=<hex>]
v1 = HMAC_SHA256(sekret, "<t>.<surowe ciało>") → zapis szesnastkowy- Liczysz HMAC z surowych bajtów ciała, zanim cokolwiek je sparsuje. Ponowna serializacja JSON-a nie odtworzy tego samego podpisu.
- Odrzucaj zapytania, których
todbiega od Twojego zegara o więcej niż ±300 s. - Przez 24 godziny po rotacji sekretu nagłówek ma dwa wpisy
v1=— najpierw dla nowego sekretu, potem dla poprzedniego. Zaakceptuj zapytanie, jeśli pasuje którykolwiek. - Porównuj podpisy w stałym czasie (
timingSafeEqual,hash_equals,hmac.compare_digest), nigdy zwykłym==. - Zapytanie z błędnym podpisem odrzuć statusem
401. Mediflo potraktuje to jak błąd i zastosuje tryb awaryjny.
Gotowe funkcje weryfikujące — te same co dla webhooków:
import { createHmac, timingSafeEqual } from 'node:crypto'/** * Verify a Mediflo webhook signature. * * Header shape: `t=<unix-seconds>,v1=<hex>[,v1=<hex>]` * where `v1 = HMAC_SHA256(secret, "<t>.<rawBody>")`. * * `rawBody` MUST be the exact bytes we sent. Re-serializing parsed JSON does * not reproduce the digest — in Express, use * `express.json({ verify: (req, _res, buf) => { req.rawBody = buf } })`. * * Pass BOTH secrets during a rotation window (or just the current one; the * header carries a `v1=` entry per active secret and any match is valid). */export function verifyMedifloSignature( header: string | undefined, rawBody: string | Buffer, secrets: string[], toleranceSeconds = 300): boolean { if (!header || secrets.length === 0) return false let timestamp: number | null = null const presented: string[] = [] for (const part of header.split(',')) { const trimmed = part.trim() const eq = trimmed.indexOf('=') if (eq <= 0) continue const key = trimmed.slice(0, eq) const value = trimmed.slice(eq + 1) if (key === 't') { if (!/^\d+$/.test(value)) return false timestamp = Number(value) } else if (key === 'v1') { // More than one entry appears during a 24 h secret rotation. presented.push(value) } } if (timestamp === null || presented.length === 0) return false // Replay protection: the timestamp is INSIDE the signed string, so an // attacker cannot move it forward without invalidating every digest. const now = Math.floor(Date.now() / 1000) if (Math.abs(now - timestamp) > toleranceSeconds) return false for (const secret of secrets) { const expected = createHmac('sha256', secret) .update(`${timestamp}.${rawBody}`) .digest('hex') for (const candidate of presented) { // Validate the hex BEFORE comparing. Buffer.from('zz', 'hex') truncates // silently, and timingSafeEqual throws RangeError on a length mismatch — // so without these two guards a malformed header crashes the handler // instead of returning false. const normalized = candidate.trim().toLowerCase() if (!/^[0-9a-f]+$/.test(normalized)) continue if (normalized.length !== expected.length) continue if ( timingSafeEqual( Buffer.from(normalized, 'hex'), Buffer.from(expected, 'hex') ) ) { return true } } } return false}Odpowiedź
Odpowiedz statusem 2xx i ciałem JSON (zalecany nagłówek Content-Type: application/json):
{ "numbers": [ { "phoneNumber": "+48600100200", "label": "dr Anna Nowak" }, "+48600100300" ]}numbersto tablica. Każda pozycja to napis z numerem albo obiekt{ "phoneNumber": "…", "label": "…" }—labeljest opcjonalny. Oba kształty można mieszać.- Kolejność = kolejność wybierania. Pierwszy numer dzwoni pierwszy.
- Mediflo wybiera najwyżej 10 numerów — pierwsze poprawne z listy. Lista może mieć do 50 pozycji; dłuższa jest w całości odrzucana jako niepoprawna.
labelma najwyżej 80 znaków (dłuższy przycinamy). Pokazujemy go w historii połączeń w Portalu, np. „Anna Nowak” zamiast samego numeru.- Zalecany format numeru to E.164 (
+48600100200). Format krajowy (600 100 200) też zadziała — interpretujemy go według kraju Twojej organizacji. - Nieznane pola ignorujemy, zarówno w głównym obiekcie, jak i w pozycjach listy.
- Pusta lista oznacza „brak numerów” i uruchamia tryb awaryjny (
no_valid_numbers). - Niepoprawna pozycja (np. liczba zamiast napisu) odpada sama (
invalid_entry); pozostałe są przetwarzane normalnie.
| Limit | Wartość |
|---|---|
| Limit czasu na odpowiedź | 3 s domyślnie; w Portalu od 1 s do 5 s |
| Maksymalny rozmiar odpowiedzi | 16 KiB |
Maksymalna liczba pozycji w numbers | 50 |
| Maksymalna liczba wybieranych numerów | 10 (pierwsze poprawne, w kolejności z listy) |
Maksymalna długość label | 80 znaków (dłuższa jest przycinana) |
Walidacja numerów
Każdy zwrócony numer jest sprawdzany osobno. Odrzucony numer nie blokuje pozostałych — Mediflo po prostu go pomija. Wynik dla każdego numeru zobaczysz po wysłaniu testowego zapytania i na liście ostatnich zapytań w Portalu.
| Powód | Znaczenie |
|---|---|
| invalid_entry | Pozycja nie jest napisem ani obiektem z polem tekstowym phoneNumber. |
| invalid_number | To nie jest poprawny numer telefonu. |
| country_unresolved | Nie udało się ustalić kraju numeru. |
| country_not_allowed | Kraj numeru nie jest dozwolony w ustawieniach połączeń organizacji. |
| premium_rate | Numer o podwyższonej opłacie. |
| mediflo_number | Numer obsługiwany przez Mediflo (np. numer kliniki przekierowany do Mediflo). Połączenie wróciłoby do kolejki. |
| caller_number | Numer osoby dzwoniącej. |
| duplicate | Numer powtórzony na liście (liczy się pierwsze wystąpienie). |
| over_limit | Numer poza limitem 10 poprawnych numerów. |
Limit 10 dotyczy numerów, które przeszły walidację: jeśli trzy pierwsze pozycje odpadną, wybierane są kolejne poprawne, aż do dziesięciu.
Limity czasu, błędy i tryb awaryjny
Limity czasu i ponowienie
Twój serwer ma domyślnie 3 sekundy na pełną odpowiedź — liczone od rozpoczęcia połączenia (razem z DNS i TLS) do ostatniego bajtu. W Portalu możesz ustawić od 1 do 5 sekund. Dzwoniący czeka w tym czasie w kolejce, więc odpowiadaj tak szybko, jak to możliwe; celuj w mniej niż sekundę.
Mediflo ponawia zapytanie najwyżej raz i tylko wtedy, gdy:
- pierwsza próba zakończyła się błędem sieci, statusem
5xxalbo429, - stało się to szybko (w mniej niż połowie limitu czasu) i zostało co najmniej 0,5 s.
Ponowienie mieści się w tym samym limicie czasu i ma to samo id. Po przekroczeniu czasu, statusie 4xx (poza 429) albo niepoprawnej odpowiedzi ponowienia nie ma.
Kody błędów
Każdy z tych przypadków oznacza, że dla tego wejścia do kolejki obowiązuje tryb awaryjny. Kod błędu widać w wyniku testowego zapytania i na liście ostatnich zapytań w Portalu.
| Kod błędu | Znaczenie |
|---|---|
| not_configured | Kolejka nie ma ustawionego adresu URL lub sekretu podpisu. |
| secret_unavailable | Nie udało się odczytać sekretu podpisu kolejki. |
| invalid_url | Adres URL jest nieprawidłowy (wymagany https, bez loginu i hasła w adresie). |
| host_not_allowed | Nie można połączyć się z hostem: nazwa nie istnieje albo wskazuje na sieć prywatną lub zastrzeżoną. |
| timeout | Brak pełnej odpowiedzi w ustawionym czasie. |
| network_error | Błąd połączenia (np. odrzucone połączenie lub błąd TLS). |
| redirect | Serwer odpowiedział przekierowaniem (3xx). Przekierowania nie są obsługiwane. |
| http_status | Serwer zwrócił status HTTP spoza zakresu 2xx. |
| response_too_large | Odpowiedź przekroczyła 16 KiB. |
| invalid_json | Odpowiedź nie jest poprawnym JSON-em. |
| invalid_body | JSON nie jest obiektem z tablicą numbers (maksymalnie 50 pozycji). |
| no_valid_numbers | Żaden zwrócony numer nie przeszedł walidacji. |
| circuit_open | Kilka zapytań z rzędu zakończyło się błędem, więc przez 60 sekund Mediflo nie wysyła zapytań i od razu stosuje tryb awaryjny. |
| interrupted | Zapytanie zostało przerwane po stronie Mediflo. |
Przerwa po serii błędów
Jeśli 5 zapytań z rzędu dla danej kolejki zakończy się błędem serwera lub połączenia (np. timeout, http_status, invalid_json), Mediflo przez 60 sekund w ogóle nie pyta Twojego serwera i od razu stosuje tryb awaryjny (circuit_open). Po tej przerwie Mediflo znów wysyła zapytania: poprawna odpowiedź przywraca normalne działanie, a kolejny błąd od razu wznawia przerwę na 60 sekund. Poprawna odpowiedź bez użytecznych numerów (np. pusta lista, no_valid_numbers) nie jest błędem serwera — przerywa serię. Zapytania testowe z Portalu nie liczą się do tej serii.
Tryb awaryjny
Tryb awaryjny dotyczy jednego wejścia do kolejki. Następny dzwoniący znów wywoła zapytanie (chyba że trwa przerwa po serii błędów).
| Tryb | Co się dzieje |
|---|---|
| Członkowie kolejki | Mediflo dzwoni do członków kolejki według ich harmonogramów — tak jak w zwykłej kolejce zewnętrznej. |
| Wyjście z kolejki | Mediflo nikogo nie wybiera. Przepływ IVR wyprowadza dzwoniącego gałęzią Przepełnienie węzła Kolejka, a jeśli jej nie ma — gałęzią Timeout. |
Zabezpieczenie przed czekaniem bez końca
Jeśli w trybie wyjście z kolejki węzeł nie ma ani gałęzi Przepełnienie, ani Timeout (albo dzwoniący trafił do kolejki inaczej niż przez przepływ IVR), po 30 sekundach Mediflo i tak zacznie dzwonić do członków kolejki.
Co się dzieje w trakcie oczekiwania
- Pętla tej samej listy. Po przejściu całej listy bez przyjęcia Mediflo zaczyna od początku, z tymi samymi numerami. Listy nie da się zmienić w trakcie oczekiwania — nowe numery zadziałają dopiero przy następnym wejściu do kolejki. Jeśli w całym przejściu żaden numer nawet nie zadzwonił, przed kolejnym Mediflo odczekuje 30 sekund.
- Odrzucenie jest zapamiętywane. Numer, który wcisnął 2 dla tego dzwoniącego, nie dostanie już kolejnego połączenia w jego sprawie.
- Zajęte numery są pomijane. Numer, pod który właśnie dzwonimy w sprawie innego dzwoniącego, albo numer w trakcie rozmowy przyjętej z kolejki zewnętrznej jest pomijany i próbowany ponownie w następnym przejściu.
- Jeden numer, wielu dzwoniących. Możesz zwrócić ten sam numer dla wielu oczekujących — Mediflo zaproponuje go zawsze tylko jednemu dzwoniącemu naraz.
- Limit połączeń. Organizacja ma jednocześnie najwyżej 10 takich połączeń wychodzących. Pozostali dzwoniący czekają na swoją kolej.
- Rozłączenie dzwoniącego kończy wszystko: trwające połączenie wychodzące jest przerywane, a kolejne nie są wykonywane.
- Nowe wejście = nowe zapytanie. Gdy dzwoniący zadzwoni ponownie albo zostanie przeniesiony do innej kolejki z numerami z API, Mediflo wyśle nowe zapytanie.
- Przypisanie rozmowy. Jeśli przyjmujący numer to telefon zewnętrzny z profilu użytkownika Portalu, rozmowa zostanie przypisana do tego użytkownika. W przeciwnym razie w historii połączeń zobaczysz numer i jego
label.
Przypadki brzegowe
Dzwoniący zastrzegł numer
data.caller.phoneNumber ma wtedy wartość null. Zwróć numery, które obsłużą każdego dzwoniącego — na przykład ogólny numer biura obsługi klienta.
Numer organizacji przekierowany do Mediflo
Takiego numeru nie wybierzemy (mediflo_number): połączenie wróciłoby do Mediflo, a często do tej samej kolejki. To samo dotyczy każdego numeru obsługiwanego przez Mediflo. Zwracaj numery, które dzwonią bezpośrednio u konsultanta — komórkę albo numer wewnętrzny centrali dostępny z zewnątrz.
Numer samego dzwoniącego
Jest odrzucany (caller_number) — nie oddzwaniamy do dzwoniącego z jego własnej kolejki.
Numery zagraniczne
Kraj numeru musi być dozwolony w ustawieniach połączeń Twojej organizacji (country_not_allowed). Jeśli potrzebujesz dzwonić za granicę, skontaktuj się z Mediflo.
Numery o podwyższonej opłacie
Są zawsze blokowane (premium_rate).
Węzeł „Sprawdzenie kolejki” w przepływie IVR
Ten węzeł sprawdza wyłącznie konsultantów zalogowanych do Portalu. Nie wie nic o numerach, które zwróci Twój serwer, więc przed kolejką z numerami z API może skierować dzwoniącego na ścieżkę „niedostępny”, mimo że ktoś pod Twoimi numerami czeka. Z tego samego powodu w kolejce z numerami z API gałąź Przepełnienie węzła Kolejka nie reaguje na brak zalogowanych konsultantów — służy tylko jako wyjście w trybie awaryjnym.
Jak wygląda zapytanie testowe
Ma nagłówek Mediflo-Event-Test: true, prawdziwy podpis, losowy identyfikator rozmowy i przykładowy numer dzwoniącego. Odpowiadaj na nie tak samo jak na prawdziwe — Portal pokaże, jak Mediflo oceni każdy zwrócony numer. Nikt nie zostanie wybrany, a za zapytaniem testowym nie stoi żaden prawdziwy dzwoniący, więc nie zapisuj go jako prawdziwej rozmowy.
Kto przyjął rozmowę?
W historii połączeń w Portalu widać, który numer przyjął rozmowę i z jaką etykietą (label).
Przykładowa implementacja
Minimalny serwer: weryfikuje podpis z surowych bajtów, sprawdza znacznik czasu, akceptuje dowolny z wpisów v1= i zwraca listę numerów. Sekret (i przez 24 h po rotacji — poprzedni) trzymaj w zmiennych środowiskowych.
import express from 'express'
import { createHmac, timingSafeEqual } from 'node:crypto'
// Bieżący sekret i — przez 24 h po rotacji — poprzedni.
const SECRETS = [process.env.MEDIFLO_QUEUE_SECRET, process.env.MEDIFLO_QUEUE_SECRET_PREVIOUS].filter(Boolean)
const TOLERANCE_SECONDS = 300
function verifySignature(header, rawBody, secrets) {
if (!header || secrets.length === 0) return false
let timestamp = null
const presented = []
for (const part of header.split(',')) {
const [key, ...rest] = part.trim().split('=')
const value = rest.join('=')
if (key === 't') {
if (!/^\d+$/.test(value)) return false
timestamp = Number(value)
} else if (key === 'v1') {
presented.push(value.trim().toLowerCase())
}
}
if (timestamp === null || presented.length === 0) return false
if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false
for (const secret of secrets) {
const expected = createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex')
for (const candidate of presented) {
// Sprawdź format przed porównaniem: timingSafeEqual rzuca wyjątek przy różnej długości.
if (!/^[0-9a-f]{64}$/.test(candidate)) continue
if (timingSafeEqual(Buffer.from(candidate, 'hex'), Buffer.from(expected, 'hex'))) return true
}
}
return false
}
// Twoja logika: grafik, dyżur, stały opiekun dzwoniącego…
function pickNumbers({ queue, caller }) {
return [{ phoneNumber: '+48600100200', label: 'Anna Nowak' }, '+48600100300']
}
const app = express()
// express.raw: podpis liczymy z SUROWYCH bajtów, przed parsowaniem JSON-a.
app.post('/mediflo/queue-numbers', express.raw({ type: 'application/json', limit: '64kb' }), (req, res) => {
if (!Buffer.isBuffer(req.body) || !verifySignature(req.get('Mediflo-Signature'), req.body, SECRETS)) {
return res.status(401).end()
}
const event = JSON.parse(req.body.toString('utf8'))
if (event.type !== 'queue.numbers.requested') return res.status(400).end()
const isTest = req.get('Mediflo-Event-Test') === 'true'
console.log(`zapytanie ${event.id}${isTest ? ' (test)' : ''}, kolejka ${event.data.queue.name}`)
res.json({ numbers: pickNumbers(event.data) })
})
app.listen(3000)Test lokalny z curl
Skrypt podpisuje przykładowe zapytanie tym samym wzorem co Mediflo i wysyła je na lokalny serwer z przykładu powyżej. Wstaw ten sam sekret, którego używa serwer: poprawny serwer odpowie 200 i listą numerów, a przy innym sekrecie — 401.
# Sekret skopiowany z ustawień kolejki w Portalu.SECRET='whsec_…'# Dokładnie te bajty są podpisywane i wysyłane (bez końcowego znaku nowej linii).BODY='{"id":"evt_7f1c2d4e-8a9b-4c3d-9e0f-1a2b3c4d5e6f","type":"queue.numbers.requested","apiVersion":"v1","occurredAt":"2026-10-01T08:15:02.120Z","data":{"queue":{"id":"3b0f6c2e-5a4d-4f1e-9c8b-7a6d5e4f3c2b","name":"Rejestracja"},"call":{"id":"9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a","calledNumber":"+48221234567","enteredQueueAt":"2026-10-01T08:15:01.950Z"},"caller":{"phoneNumber":"+48600111222"}}}'T=$(date +%s)SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')curl -sS -i -X POST http://localhost:3000/mediflo/queue-numbers \ -H 'Content-Type: application/json' \ -H "Mediflo-Signature: t=$T,v1=$SIG" \ -H 'Mediflo-Event-Id: evt_7f1c2d4e-8a9b-4c3d-9e0f-1a2b3c4d5e6f' \ -H 'Mediflo-Event-Type: queue.numbers.requested' \ -H 'Mediflo-Event-Test: true' \ --data-binary "$BODY"Lista kontrolna przed uruchomieniem
- Endpoint działa pod publicznym adresem
https://, bez przekierowań i bez loginu ani hasła w adresie. - Podpis jest weryfikowany z surowych bajtów, z tolerancją ±300 s, i akceptuje dowolny z wpisów
v1=. - Sekret kolejki leży w konfiguracji serwera (nie w kodzie ani w logach); wiesz, jak podmienić go po rotacji.
- Odpowiedź przychodzi wyraźnie przed limitem czasu (celuj w mniej niż sekundę), także przy obciążeniu.
- Zwracasz numery w kolejności wybierania, najlepiej w formacie E.164, z czytelnym
label. - Nie zwracasz numerów, które prowadzą z powrotem do Mediflo (np. numeru organizacji przekierowanego do Mediflo).
- Masz odpowiedź dla zastrzeżonego numeru (
phoneNumber: null). - Wybrany tryb awaryjny pasuje do Twojej organizacji; przy „wyjściu z kolejki” węzeł Kolejka ma gałąź Przepełnienie lub Timeout.
- Przed kolejką nie stoi węzeł „Sprawdzenie kolejki”, który odeśle dzwoniących, bo nikt nie jest zalogowany do Portalu.
- Testowe zapytanie z Portalu kończy się sukcesem, a każdy numer ma oczekiwany wynik walidacji.
- Nie logujesz pełnych numerów dzwoniących dłużej, niż to konieczne — to dane osobowe.