Dokumentacja dla deweloperów
Palyri Leads API
Wysyłaj leady do Palyri z dowolnego źródła: własnego lejka, Make.com, n8n, Zapiera albo backendu. Odsyłaj z powrotem informację, który lead się skonwertował i ile zostawił. Wszystkie dane liczą się w statystykach Palyri dokładnie tak samo, jak leady z naszych własnych lejków.
Zacznij tutaj
Potrzebujesz jednego tokenu. Wygeneruj go w panelu: Ustawienia → Integracje → Tokeny API. Token jest pokazywany raz. W bazie trzymamy tylko jego skrót.
curl -X POST 'https://app.palyri.com/api/leads/make-ingest' \
-H 'X-Palyri-Token: pal_xxxxxxxxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{
"source": "Quiz",
"external_id": "zgloszenie-8842",
"ads_consent": true,
"data": {
"imię": "Anna",
"telefon": "500600700",
"email": "anna@example.com",
"Jaki masz problem?": "Zmarszczki"
},
"campaign_attribution": {
"utm_campaign": "Botoks Q3",
"utm_content": "Kreacja A",
"ad_id": "120210000000000000"
}
}'?dry_run=1 do adresu. Odpowiemy tym, jak odczytaliśmy pola i czego brakuje, bez tworzenia leada.Uwierzytelnianie
Każde żądanie niesie nagłówek X-Palyri-Token. Token jest przypisany do jednej kliniki i ma zakres uprawnień: leads:write (dodawanie leadów) oraz opcjonalnie conversions:write (raportowanie przychodu).
Dla integracji o podwyższonych wymaganiach włącz przy tworzeniu tokenu podpis HMAC. Wtedy sam token przestaje wystarczać: żądanie musi nieść jeszcze X-Palyri-Timestamp (czas uniksowy w sekundach) i X-Palyri-Signature. Podpis wiąże żądanie z jego treścią i czasem, więc przechwycony request nie da się odtworzyć po upływie 5 minut.
// Node.js
const crypto = require("crypto")
const body = JSON.stringify(payload)
const timestamp = Math.floor(Date.now() / 1000)
const signature = crypto
.createHmac("sha256", SIGNING_SECRET)
.update(`${timestamp}.${body}`)
.digest("hex")
await fetch("https://app.palyri.com/api/leads/make-ingest", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Palyri-Token": TOKEN,
"X-Palyri-Timestamp": String(timestamp),
"X-Palyri-Signature": `sha256=${signature}`,
},
body,
})Dodawanie leadów
https://app.palyri.com/api/leads/make-ingestPrzyjmujemy trzy kształty payloadu i sami je normalizujemy: natywny format Meta Lead Ads (field_data), format modułu Facebook Lead Ads z Make.com (mappable_field_data) oraz najprostszy, czyli obiekt data z parami pole/wartość. Nazwy pól rozpoznajemy po polsku i po angielsku.
| Pole | Typ | Opis |
|---|---|---|
data | obiekt | Pola leada. Imię/telefon/e-mail rozpoznajemy automatycznie; każde inne pole zapisujemy jako pytanie i odpowiedź. Wartości mogą być tekstem, liczbą, wartością logiczną lub tablicą (wielokrotny wybór). |
source | tekst | Etykieta źródła, np. "Quiz". Domyślnie "Meta Ads". |
external_id | tekst | Twój własny klucz idempotencji. Ponowne wysłanie tego samego id zwraca istniejącego leada zamiast tworzyć drugiego. |
campaign_attribution | obiekt | UTM-y, identyfikatory reklam i clickid. Szczegóły w sekcji Atrybucja reklam. |
ads_consent | bool | Czy formularz zebrał zgodę marketingową. Bez wartości true nie wysyłamy do Meta żadnych zdarzeń tego leada. |
fbp / fbc / fbclid | tekst | Ciasteczka _fbp i _fbc oraz fbclid z adresu URL. Wiążą późniejszą konwersję z kliknięciem w reklamę. |
event_id | tekst | Identyfikator zdarzenia dla Meta. Przyślij ten sam, którym strzela Twój piksel, a Meta scali obie kopie w jedną konwersję. |
value / currency | liczba / tekst | Wartość zgłoszenia, jeśli znana już w momencie zapisu. Waluta w formacie ISO 4217, np. PLN. |
funnel_id | uuid | Lejek Palyri, do którego przypisać leada. Ignorowany, jeśli nie należy do Twojej kliniki. |
Odpowiedź:
HTTP 200
{ "ok": true, "accepted": true }Ostatnie przychodzące.Duplikaty odrzucamy na trzy sposoby: po leadgen_id z Meta, po Twoim external_id oraz, jako siatka bezpieczeństwa dla zgłoszeń bez żadnego identyfikatora, po tym samym numerze telefonu w oknie 10 minut.
Atrybucja reklam
To jest część, która decyduje o tym, czy Palyri policzy koszt pozyskania leada. Potrzebujemy identyfikatora reklamy, a nie samej nazwy: nazwę można w Meta zmienić w locie, identyfikator nigdy się nie zmienia.
W Menedżerze reklam ustaw parametry adresu URL dla reklamy prowadzącej na Twój lejek:
utm_source=facebook
utm_medium=paid
utm_campaign={{campaign.name}}
utm_term={{adset.name}}
utm_content={{ad.name}}
ad_id={{ad.id}}
adset_id={{adset.id}}
campaign_id={{campaign.id}}Twój lejek odczytuje te parametry z adresu i przesyła je nam razem z leadem, w obiekcie campaign_attribution albo jako pola najwyższego poziomu. Obie formy działają.
Zgoda i powrót konwersji do Meta
Palyri odsyła do Meta zdarzenia z Conversions API, żeby kampania optymalizowała się na tym, co realnie się wydarzyło, a nie tylko na kliknięciach. Wysyłamy trzy zdarzenia:
| Zdarzenie | Kiedy | Opis |
|---|---|---|
Lead | przy zapisie | Zgłoszenie trafiło do CRM. Wysyłane od razu po przyjęciu leada. |
Schedule | przy depozycie | Lead wpłacił depozyt. Sygnał jakościowy, po którym Meta szuka podobnych osób. |
Purchase | przy przychodzie | Lead zapłacił za zabieg. Niesie wartość i walutę, więc Meta umie policzyć zwrot z kampanii. |
"ads_consent": true nie wyślemy żadnego z tych zdarzeń. To nadal profilowanie reklamowe, bo do Meta lecą zahashowany e-mail i telefon, więc brak pola traktujemy jak brak zgody, a nie jak zgodę domyślną.Jakość dopasowania podnosisz, przesyłając fbp, fbc i fbclid. Depozyt wpada do CRM kilka dni po zgłoszeniu, gdy nie ma już żadnej przeglądarki ani ciasteczek, zapisujemy je więc przy zgłoszeniu i odtwarzamy przy każdym późniejszym zdarzeniu.
Jeśli Twój lejek strzela własnym pikselem, prześlij nam ten sam event_id. Meta zdeduplikuje parę i policzy jedną konwersję zamiast dwóch.
Raportowanie przychodu
https://app.palyri.com/api/leads/conversionDepozyty i przychody wpisane w Palyri lecą do Meta automatycznie. Ten endpoint jest dla sprzedaży, która domyka się poza Palyri: w Twoim systemie, Twojej kasie. Wymaga tokenu z uprawnieniem conversions:write.
curl -X POST 'https://app.palyri.com/api/leads/conversion' \
-H 'X-Palyri-Token: pal_xxxxxxxxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{
"external_id": "zgloszenie-8842",
"event_name": "Purchase",
"value": 2400,
"currency": "PLN",
"update_lead": true
}'| Pole | Typ | Opis |
|---|---|---|
lead_id / external_id / phone / email | tekst | Sposób wskazania leada. Sprawdzamy w tej kolejności, pierwsze trafienie wygrywa. |
event_name | tekst | Purchase (domyślne), Schedule, CompleteRegistration, Contact, SubmitApplication, StartTrial, Subscribe. |
value / currency | liczba / tekst | Wartość konwersji i waluta ISO 4217. |
event_id | tekst | Własny identyfikator zdarzenia. Domyślnie jedno zdarzenie danego typu na leada, tym samym wzorem, którego używa Palyri. Dzięki temu ten sam przychód zgłoszony z obu stron liczy się raz. Kolejny, osobny zakup zgłoś własnym event_id. |
update_lead | bool | Gdy true, wartość zapisujemy też na leadzie, żeby raporty przychodowe w Palyri pokazywały to samo, co Meta. |
HTTP 202
{ "ok": true, "lead_id": "…", "event_name": "Purchase", "event_id": "purchase-…", "queued": true }202znaczy „przyjęte do wysyłki”, nie „doręczone do Meta”. Zdarzenie idzie kolejką z ponowieniami, a stan doręczenia widać w panelu integracji.Webhooki wychodzące
Druga strona integracji: Palyri wysyła żądanie do Ciebie, gdy coś dzieje się z leadem. Endpoint rejestrujesz w panelu (Ustawienia → Integracje → Webhooki wychodzące) i dostajesz sekret do weryfikacji podpisu.
| Zdarzenie | Typ | Opis |
|---|---|---|
lead.created | zdarzenie | Lead trafił do CRM z dowolnego źródła, także z Twojego webhooka. |
lead.status_changed | zdarzenie | Zmienił się status leada. Payload niesie stary i nowy status. |
lead.converted | zdarzenie | Lead pierwszy raz zapisał niezerowy przychód. Payload niesie wartość i walutę. |
Przykładowe żądanie, które wyślemy:
POST https://your-server.example/palyri
Content-Type: application/json
X-Palyri-Event: lead.status_changed
X-Palyri-Delivery: 3f2b… // delivery id, stable across retries
X-Palyri-Timestamp: 1787327270
X-Palyri-Signature: sha256=…
{
"event": "lead.status_changed",
"delivery_id": "3f2b…",
"sent_at": "2026-08-21T15:44:15.155Z",
"clinic_id": "…",
"data": {
"lead": {
"id": "…",
"name": "Anna Kowalska",
"phone": "+48500600700",
"email": "anna@example.com",
"status": "deposit_paid",
"source": "Meta Ads",
"ad_name": "Kreacja A",
"external_id": "zgloszenie-8842",
"treatment_value": null,
"campaign_attribution": { "utm_campaign": "Botoks Q3" }
},
"status": "deposit_paid",
"previous_status": "contacted"
}
}Weryfikacja podpisu po Twojej stronie:
// Node.js: the comparison MUST be constant-time
const crypto = require("crypto")
function verify(req, rawBody, SECRET) {
const ts = Number(req.headers["x-palyri-timestamp"])
if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false
const expected = crypto
.createHmac("sha256", SECRET)
.update(`${ts}.${rawBody}`)
.digest("hex")
const got = String(req.headers["x-palyri-signature"]).replace(/^sha256=/, "")
const a = Buffer.from(expected), b = Buffer.from(got)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}Nieudaną dostawę ponawiamy sześć razy z narastającą przerwą (1 min, 5 min, 25 min, ~2 h, ~10 h). X-Palyri-Delivery jest stałe między ponowieniami, więc użyj go jako klucza idempotencji, bo to samo zdarzenie może przyjść u Ciebie dwa razy.
4xx (poza 408 i 429) traktujemy jako błąd konfiguracji i nie ponawiamy, bo to oszczędza Twój serwer i nasze przebiegi. Przekierowania nie są podążane: podaj adres docelowy wprost.Limity i błędy
| Kod | Kategoria | Opis |
|---|---|---|
200 / 202 | sukces | Żądanie przyjęte. |
400 | błąd payloadu | Nieprawidłowy JSON albo nieobsługiwana nazwa zdarzenia lub waluta. |
401 | uwierzytelnianie | Brak tokenu, token nieznany, odwołany lub wygasły, albo podpis HMAC się nie zgadza. |
403 | uprawnienia | Odbieranie leadów wyłączone w panelu albo token bez wymaganego zakresu. |
404 | nie znaleziono | Tylko przy raportowaniu konwersji: nie umiemy dopasować leada. |
413 | payload | Ciało żądania przekracza 256 kB. |
429 | limit | Ponad 120 żądań na minutę na token. Nagłówek Retry-After mówi, kiedy spróbować ponownie. |
Po rotacji tokenu w panelu stary działa jeszcze przez 48 godzin, żeby dało się go podmienić w scenariuszach bez przerwy w napływie leadów. W odpowiedzi dostajesz wtedy pole warning. Jeśli token wyciekł, unieważnij stary natychmiast. Przycisk jest obok ostrzeżenia w panelu.