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"
    }
  }'
Zanim wepniesz to na produkcji, dopisz ?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).

Token to hasło. Trzymaj go po stronie serwera: w Make.com, n8n albo we własnym backendzie. Nigdy w kodzie strony WWW, bo staje się wtedy publiczny i każdy może wstrzykiwać leady.

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

POSThttps://app.palyri.com/api/leads/make-ingest

Przyjmujemy 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.

PoleTypOpis
dataobiektPola 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).
sourcetekstEtykieta źródła, np. "Quiz". Domyślnie "Meta Ads".
external_idtekstTwój własny klucz idempotencji. Ponowne wysłanie tego samego id zwraca istniejącego leada zamiast tworzyć drugiego.
campaign_attributionobiektUTM-y, identyfikatory reklam i clickid. Szczegóły w sekcji Atrybucja reklam.
ads_consentboolCzy formularz zebrał zgodę marketingową. Bez wartości true nie wysyłamy do Meta żadnych zdarzeń tego leada.
fbp / fbc / fbclidtekstCiasteczka _fbp i _fbc oraz fbclid z adresu URL. Wiążą późniejszą konwersję z kliknięciem w reklamę.
event_idtekstIdentyfikator zdarzenia dla Meta. Przyślij ten sam, którym strzela Twój piksel, a Meta scali obie kopie w jedną konwersję.
value / currencyliczba / tekstWartość zgłoszenia, jeśli znana już w momencie zapisu. Waluta w formacie ISO 4217, np. PLN.
funnel_iduuidLejek Palyri, do którego przypisać leada. Ignorowany, jeśli nie należy do Twojej kliniki.

Odpowiedź:

HTTP 200
{ "ok": true, "accepted": true }
Dlaczego 200 zamiast identyfikatora leada: moduł HTTP w Make.com przerywa połączenie po 40 sekundach. Potwierdzamy przyjęcie od razu po sprawdzeniu tokenu i payloadu, a resztę (zapis, kupon, powiadomienia, wysyłkę do Meta) robimy zaraz po odesłaniu odpowiedzi. Efekt zapisu widać w panelu w sekcji 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ą.

Warunek policzenia kosztu: identyfikator reklamy sprawdzamy względem kont reklamowych podłączonych do Palyri. Jeśli konto nie jest jeszcze podłączone, zapiszemy etykiety kampanii, kreacji i zestawu reklam, ale kolumna z kosztem pozyskania zostanie pusta, bo nie mamy skąd wziąć danych o wydatkach. Po podłączeniu konta nocna synchronizacja dokleja wydatki także do leadów zebranych wcześniej.

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:

ZdarzenieKiedyOpis
Leadprzy zapisieZgłoszenie trafiło do CRM. Wysyłane od razu po przyjęciu leada.
Scheduleprzy depozycieLead wpłacił depozyt. Sygnał jakościowy, po którym Meta szuka podobnych osób.
Purchaseprzy przychodzieLead zapłacił za zabieg. Niesie wartość i walutę, więc Meta umie policzyć zwrot z kampanii.
Bez "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

POSThttps://app.palyri.com/api/leads/conversion

Depozyty 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
  }'
PoleTypOpis
lead_id / external_id / phone / emailtekstSposób wskazania leada. Sprawdzamy w tej kolejności, pierwsze trafienie wygrywa.
event_nametekstPurchase (domyślne), Schedule, CompleteRegistration, Contact, SubmitApplication, StartTrial, Subscribe.
value / currencyliczba / tekstWartość konwersji i waluta ISO 4217.
event_idtekstWł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_leadboolGdy 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.

ZdarzenieTypOpis
lead.createdzdarzenieLead trafił do CRM z dowolnego źródła, także z Twojego webhooka.
lead.status_changedzdarzenieZmienił się status leada. Payload niesie stary i nowy status.
lead.convertedzdarzenieLead 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)
}
Odpowiadaj szybko. Każdy status 2xx to potwierdzenie odbioru. Mamy 8 sekund cierpliwości. Jeśli Twoja obsługa trwa dłużej, zapisz zdarzenie u siebie i przetwarzaj je asynchronicznie, zamiast trzymać nasze połączenie.

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.

Endpoint, który zawiedzie 20 razy z rzędu, wyłączamy i pokazujemy to w panelu. Odpowiedź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

KodKategoriaOpis
200 / 202sukcesŻądanie przyjęte.
400błąd payloaduNieprawidłowy JSON albo nieobsługiwana nazwa zdarzenia lub waluta.
401uwierzytelnianieBrak tokenu, token nieznany, odwołany lub wygasły, albo podpis HMAC się nie zgadza.
403uprawnieniaOdbieranie leadów wyłączone w panelu albo token bez wymaganego zakresu.
404nie znalezionoTylko przy raportowaniu konwersji: nie umiemy dopasować leada.
413payloadCiało żądania przekracza 256 kB.
429limitPonad 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.