Jak zadzwonić do klienta z workflow n8n
September 2, 2026 · 7 minut czytania
Z n8n można zadzwonić do klienta bez pisania ani jednej linijki kodu: proszę dodać do workflow węzeł HTTP Request i wskazać w nim POST https://volai.cz/v1/calls z nagłówkiem Authorization: Bearer vk_... i treścią {to, agentId} - a agent głosowy wykręci numer dokładnie wtedy, gdy workflow tego potrzebuje. Poniżej cała konfiguracja: jak przenieść dane z workflow do promptu agenta, jak odebrać wynik rozmowy z powrotem w n8n oraz jak dzwonić w ogóle bez agenta, łącząc bezpośrednio dwa numery.
Czego potrzeba
- Instancji n8n - w chmurze lub self-hosted, w dowolnej nowszej wersji.
- Konta volai i klucza API z portalu (Ustawienia -> API i MCP) w postaci
vk_.... Proszę zapisać go w n8n jako credential typu Header Auth (nagłówekAuthorization, wartośćBearer vk_...), żeby nie wpisywać klucza osobno w każdym węźle. - Własnego numeru telefonu z przypisanym agentem głosowym - można go utworzyć w portalu lub przez /docs/agent. Nie ma jeszcze agenta? Nadal można dzwonić, łącząc bezpośrednio dwa numery (wariant z polem
fromponiżej).
1Dodanie węzła HTTP Request
Metoda POST, adres URL https://volai.cz/v1/calls, uwierzytelnianie zapisanym credentialem, typ treści (Body Content Type) JSON. W treści wymagane są to (numer klienta) i agentId (identyfikator agenta z portalu) - oba obowiązkowe, a z agentId/from/systemPrompt można podać dokładnie JEDNO, nigdy więcej niż jedno.
{
"to": "={{ $json.phone }}",
"agentId": "ag_kx91fa2b",
"variables": {
"customer_name": "={{ $json.name }}",
"order_number": "={{ $json.order }}"
},
"ringingTimeoutSecs": 30
}Wartości zapisane jako ={{...}} to wyrażenia n8n - $json odczytuje pola z poprzedniego węzła workflow (na przykład z webhooka „nowe zamówienie” albo z wiersza arkusza).
2Przeniesienie danych z workflow do promptu agenta
variables to obiekt typu tekst-na-tekst, który agent otrzymuje jako zmienne dynamiczne - w prompcie systemowym odwołuje się się do nich jako {{customer_name}}. Limity: najwyżej 20 kluczy, klucze do 64 znaków, wartości do 512 znaków. Nazwy attempt_id, caller_number i called_number są zarezerwowane - volai wypełnia je samo, a pasujące wartości z workflow zostałyby odrzucone.
ringingTimeoutSecs (od 5 do 60 sekund, domyślnie 25) określa, jak długo telefon ma dzwonić, zanim nieodebrane połączenie zakończy się wartością endReason: "no_answer". Dłuższy czas zwiększa szansę, że klient odbierze, ale też dłużej zajmuje wychodzący slot.
3Odczyt odpowiedzi i opcjonalne czekanie na wynik
Poprawna odpowiedź wraca od razu, ale rozmowa dopiero się zaczęła - status to initiated, a nie stan końcowy:
{
"id": "c_9d4e2b7f",
"status": "initiated"
}Jeśli na wynik ma się czekać w tym samym przebiegu workflow, zamiast korzystać z osobnego webhooka, wystarczy dodać tuż po węźle HTTP Request drugi węzeł, który wywołuje GET /v1/calls/{id} z parametrem waitSecs (od 0 do 45 sekund) - serwer odpowie, gdy rozmowa się zakończy albo upłynie limit czasu, zależnie od tego, co nastąpi pierwsze:
curl "https://volai.cz/v1/calls/c_9d4e2b7f?waitSecs=30" \
-H "Authorization: Bearer vk_YOUR_KEY"Odbiór wyniku: webhook do n8n
W produkcyjnym workflow zwykle wygodniej nie czekać w trakcie przebiegu, tylko dostarczyć wynik osobno: proszę dodać w n8n węzeł Webhook (nowy workflow w zupełności wystarczy), skopiować jego Production URL i jednorazowo zarejestrować go w volai:
curl -X PUT https://volai.cz/v1/webhook \
-H "Authorization: Bearer vk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-instance.app.n8n.cloud/webhook/volai-calls",
"events": ["call.completed", "call.failed", "call.no_answer"]
}'Odpowiedź zawiera secret (w postaci whsec_...) - proszę go zapisać, będzie potrzebny do weryfikacji podpisu każdego przychodzącego żądania. volai wysyła nagłówek Volai-Signature w postaci t=<unix>,v1=<hex> - pełny kod weryfikacji w TypeScript i Pythonie znajduje się na stronie /docs/webhooky. Nie przenosi się on do węzła Code w n8n bez zmian: HMAC liczy się z SUROWEJ treści żądania, więc w węźle Webhook trzeba włączyć opcję Raw Body (bez niej n8n przekazuje już sparsowany JSON i podpis nigdy się nie zgodzi), a moduł crypto w węźle Code trzeba załadować przez require, nie import. Treści nie należy przetwarzać przed zweryfikowaniem podpisu.
To, co przychodzi po zakończeniu rozmowy:
{
"event": "call.completed",
"ts": 1756111640000,
"data": {
"id": "c_9d4e2b7f",
"direction": "out",
"from": "+420601234567",
"to": "+420777123456",
"status": "completed",
"durationSecs": 90,
"priceHal": 513,
"answeredBy": "human",
"endReason": "completed",
"transcript": [
{ "role": "agent", "message": "Dzień dobry, dzwonię w sprawie zamówienia A-42." },
{ "role": "caller", "message": "Tak, już wiem, dziękuję za przypomnienie." }
],
"summary": "Klient potwierdził odbiór zamówienia A-42.",
"data": {
"order_confirmed": true
}
}
}Klucz data zagnieżdżony wewnątrz data jest opcjonalny i zawiera dokładnie te pola, które skonfigurowano agentowi w jego dataFields - powyższy przykład (order_confirmed) jest tylko ilustracją, w Państwa przypadku będzie inaczej. Kolejny węzeł n8n może wtedy rozgałęziać przebieg według tych pól, na przykład aktualizować rekord w CRM albo przełożyć rozmowę na inny termin.
Jak dzwonić bez agenta?
Gdy agent głosowy nie jest potrzebny, a chodzi tylko o bezpośrednie połączenie dwóch numerów (na przykład handlowca z klientem), proszę wysłać from zamiast agentId:
{
"to": "+420777123456",
"from": "+420601234567"
}volai dzwoni najpierw na from, a gdy ktoś odbierze, wybiera to. Obie nogi połączenia są rozliczane osobno po 0.92 CZK za minutę (bez dopłaty za agenta). from to numer, na którym odbierają SAMI Państwo - zwykle komórka - i może to być dowolny numer czeski lub słowacki, nie musi należeć do konta volai. Prawdziwy wymóg jest inny: konto musi mieć co najmniej jeden własny numer volai, bo to przez niego połączenie jest rozliczane i to on wyświetla się obu stronom (bez niego volai zwróci bridge_needs_number). Tylko nie należy wpisywać do from numeru volai, który jest skierowany na agenta albo na SIP - sieć połączyłaby tę pierwszą nogę z agentem zamiast z Państwem i telefon nigdy by nie zadzwonił.
Ile kosztuje takie połączenie?
Połączenie wychodzące przez agenta jest rozliczane jako suma dwóch stawek: minuty połączenia wychodzącego (0.92 CZK) plus dopłaty za agenta NA TO (2.50 CZK), obie według cennika z 2 września 2026. Połączenie trwające 90 sekund kosztuje 5.13 CZK:
| Pozycja | Stawka | Rozmowa 90 s |
|---|---|---|
| Minuta połączenia wychodzącego | 0.92 CZK/min | 1.38 CZK |
| Dopłata za agenta | 2.50 CZK/min | 3.75 CZK |
| Razem | 5.13 CZK |
Rozliczenie następuje co do sekundy, a nie za całe minuty - krótsza rozmowa kosztuje proporcjonalnie mniej. Pełny cennik (numery, agent, SMS) jest na stronie /cenik.
Bezpieczne ponawianie (Idempotency-Key)
n8n potrafi automatycznie ponowić węzeł po błędzie (Retry On Fail) - bez zabezpieczenia oznaczałoby to dwa prawdziwe telefony do tego samego klienta. Należy dodać nagłówek Idempotency-Key o wartości stałej w obrębie jednego przebiegu workflow (n8n udostępnia ją jako {{ $execution.id }}, można też użyć własnego numeru zamówienia) - przy tym samym kluczu w ciągu 24 godzin otrzymuje się z powrotem odpowiedź z pierwszej próby, a połączenie nie zostanie wykonane dwa razy:
curl -X POST https://volai.cz/v1/calls \
-H "Authorization: Bearer vk_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reminder-order-A-42" \
-d '{"to": "+420777123456", "agentId": "ag_kx91fa2b"}'Co zrobić, gdy API zwróci błąd?
Tabela obejmuje warianty agentId i from - stany charakterystyczne dla rozmowy próbnej bez własnego numeru ( systemPrompt) są celowo pominięte, to inny przepływ.
| Status | Kod | Co oznacza |
|---|---|---|
| 400 | invalid_number | Pole to nie zawiera prawidłowego numeru telefonu. |
| 400 | validation | Brakuje agentId/from albo podano oba; albo variables przekraczają limity, albo ringingTimeoutSecs jest poza zakresem 5 do 60. |
| 400 | on_dnc | Numer znajduje się na Państwa liście zakazu dzwonienia (/v1/dnc). |
| 400 | destination_auto_blocked | Ten cel został automatycznie zablokowany na 30 dni po powtarzających się niepowodzeniach. |
| 400 | cannot_call_own_number | Ochrona przed pętlą - nie można zadzwonić na własny numer volai. |
| 404 | agent_not_found | agentId nie istnieje albo nie należy do Państwa konta. |
| 404 | agent_no_number | Agent nie ma przypisanego numeru telefonu. |
| 404 | bridge_needs_number | Dla wariantu z from: konto musi mieć co najmniej jeden numer volai. |
| 409 | destination_busy | Trwa już inna rozmowa z tym samym numerem. |
| 402 | insufficient_credit | Saldo nie wystarcza na minimum potrzebne do rozpoczęcia rozmowy. |
| 503 | capacity_busy | Wszystkie linie wychodzące są teraz zajęte, proszę spróbować ponownie za minutę. |
Ogólny limit API
Poza powyższą tabelą obowiązuje jeden limit dla całego API: najwyżej 60 żądań na minutę na klucz API (HTTP 429, rate_limited). Warto o tym pamiętać w workflow, które wybiera dziesiątki numerów jeden po drugim - ten sam limit dotyczy POST /v1/messagesi każdego innego endpointu pod tym samym kluczem.
Co dalej
Pełny przegląd tego, co można robić z n8n (SMS, numery, lista zakazu dzwonienia), znajduje się w poradniku integracji z n8n. Pełna dokumentacja REST jest na stronie /docs/api, a kompletna lista zdarzeń webhooków wraz z weryfikacją podpisu na stronie /docs/webhooky.
Wypróbuj volai - pierwsze 50 kr na nasz koszt
Konto zakładają Państwo w minutę i od razu mają na nim saldo startowe na połączenia i numer - SMS-y wyślą Państwo po pierwszym doładowaniu.
Powiązane artykuły
Pięć pytań przed zakupem recepcjonisty AI
Cena, uruchomienie w 5 minut, przekierowanie numeru w O2/T-Mobile/Vodafone, numery słowackie i VAT - pięć odpowiedzi prosto z cennika i ustawień volai.
Wysyłanie SMS przez API w 10 minut w Node.js
Jak wysłać SMS z Node.js przez REST API volai: jedno żądanie fetch na POST /v1/messages, idempotencja, stany błędów i dokładna cena za segment wiadomości.
Agent ElevenLabs na czeskim numerze przez SIP
Krok po kroku: jak podłączyć agenta ElevenLabs z własnego workspace do czeskiego numeru volai przez SIP trunk - rozmowy przychodzące i wychodzące, bez dopłaty za agenta.