Konfiguracja

Webhooks — powiadomienia o zdarzeniach w zamówieniach

SalesCRM wysyła webhook (POST HTTP) za każdym razem gdy zamówienie zmieni status na skonfigurowany przez Ciebie. Dzięki temu Twoja zewnętrzna aplikacja może w czasie rzeczywistym reagować na sprzedaż, anulowania i zwroty.

1. Do czego służą webhooki

Webhook to automatyczne powiadomienie HTTP wysyłane przez SalesCRM do wskazanego przez Ciebie URL-a. Możesz go wykorzystać do:

2. Konfiguracja w panelu admina

Wejdź w Konfiguracja → Webhooki i kliknij „Dodaj webhook”. Pojedynczy webhook to jedna kombinacja statusu + URL-a + klucza.

PoleOpis
AktywnośćCheckbox włączający/wyłączający webhook bez usuwania konfiguracji.
Status zamówieniaStatus na który ma reagować webhook (np. Opłacone, Anulowane, Zwrócone, Elektroniczne lub własny). Każdy status to osobny webhook record — możesz mieć wiele wskazujących na ten sam URL.
Typ webhookaZaawansowany JSON — rekomendowany; wysyła pełny payload jako application/json z podpisem HMAC. Alternatywa: Key-value (form-urlencoded) — uproszczona forma dla starszych integracji.
Adres URLPełny URL endpointu Twojej aplikacji (musi być HTTPS w produkcji).
Klucz zabezpieczającyWygenerowany losowo sekret. SalesCRM podpisuje nim payload (HMAC-SHA256), Twoja aplikacja go weryfikuje. Trzymaj w sekrecie — każdy kto go ma może udawać webhooki SalesCRM.

Typowa konfiguracja dla platformy członkowskiej

Jeśli budujesz aplikację która przyznaje/wycofuje dostęp na podstawie zamówień, skonfiguruj trzy webhooki na ten sam URL (z tym samym kluczem):

Twoja aplikacja rozróżnia eventy po polu status w payloadzie. To pole zawsze niesie status, pod który webhook jest podpięty – nawet jeśli zamówienie zdążyło już przejść do kolejnego statusu, zanim powiadomienie wyszło.

Webhook przy każdej próbie pobrania subskrypcji

Jeśli w planie subskrypcji ustawisz status zamówienia po nieudanym pobraniu i podepniesz pod ten status webhook, powiadomienie przyjdzie po każdej z trzech prób - choć status zmienia się tylko raz. Ładunek niesie wtedy blok charge_attempt z numerem próby i polem final, więc odróżnisz „spróbujemy ponownie” od „to była ostatnia próba” i dopasujesz treść wiadomości do klienta.

Webhook tylko dla wybranych produktów

Webhook możesz zawęzić do zamówień z wybranymi produktami. Bez wybranych produktów webhook idzie przy każdym zamówieniu w danym statusie. Jeśli usuniesz albo ukryjesz ostatni produkt z listy, webhook wyłączy się sam, zamiast zacząć wysyłać dane przy każdym zamówieniu.

3. Format payloadu (Zaawansowany JSON)

Webhook wysyła POST z nagłówkami Content-Type: application/json oraz podpisem w dwóch nagłówkach: SHOPLO_HMAC_SHA256 i X-Shoplo-Hmac-Sha256 (szczegóły w sekcji 5). Ciało żądania ma strukturę:

{
  "order": {
    "order_identifier": "ZAM-1234",
    "status": "Opłacone",
    "placed_at": "2026-05-22T10:00:00Z",
    "email_address": "klient@example.pl",
    "first_name": "Jan",
    "last_name": "Kowalski",
    "phone": "+48123456789",
    "newsletter": "true",
    "delivery_address": "ul. Przykładowa 1",
    "delivery_city": "Warszawa",
    "delivery_postal_code": "00-001",
    "delivery_country": "PL",
    "invoice_company_name": null,
    "invoice_nip": null,
    "payment_type": "TpayPaymentType",
    "delivery_type": "UserDeliveryType",
    "currency": "PLN",
    "products_cost": "180.00",
    "delivery_cost": "15.00",
    "payment_cost": "5.00",
    "discount": "0.00",
    "total": "200.00",
    "discount_code_code": "",
    "order_items": [
      {
        "product_id": 42,
        "name": "ImkerClub miesięczny",
        "sku": "IMKERCLUB-30",
        "unit_price": "30.00",
        "vat_rate": "23",
        "quantity": 1,
        "discount": "0.00",
        "subscriber_identifier": "SUB-abc123",
        "subscription_period_days": 30,
        "ref": null,
        "gift_packing": false
      },
      {
        "product_id": 88,
        "name": "Książka 'Rok Twórcy'",
        "sku": "ROK-TW-PRINT",
        "unit_price": "50.00",
        "vat_rate": "5",
        "quantity": 1,
        "discount": "0.00",
        "ref": null,
        "gift_packing": false
      }
    ],
    "order_attributes": [],
    "consents": {
      "marketing_agreements": [
        { "id": 15, "kind": "newsletter", "text": "Chcę dostawać newsletter" }
      ],
      "shop_rules_version": { "id": 7, "version_date": "2026-10-01T09:00:00+02:00", "source_url": null },
      "electronic_order_acceptance": { "text": "Wyrażam zgodę na dostarczenie treści cyfrowych...", "version_date": "2026-10-13T22:10:00+02:00" }
    }
  }
}

4. Pola payloadu — szczegóły

Pola na poziomie order

PoleTypOpis
order_identifierstringIdentyfikator zamówienia widoczny w panelu admina (np. ZAM-1234).
statusstringNazwa statusu który wywołał webhook (np. Opłacone, Anulowane).
placed_atdatetime (ISO 8601)Data złożenia zamówienia.
email_addressstringEmail klienta (zawsze lower-case).
first_name, last_namestringImię i nazwisko z adresu dostawy.
total, products_cost, delivery_cost, payment_cost, discountdecimal (string)Kwoty w walucie zamówienia. total to suma do zapłaty.
currencystringAktualnie zawsze PLN.
payment_type, delivery_typestringKlasa typu płatności/dostawy (np. TpayPaymentType).
discount_code_codestringKod rabatowy użyty w zamówieniu, lub pusty string.
subscriber_identifierstringOpcjonalne — obecne tylko gdy całe zamówienie jest powiązane z subskrypcją.
service_refstringOpcjonalne — stały klucz usługi, wspólny dla całego łańcucha odnowień. Obecny w planach z identyfikacją usług po zamówieniu; w odróżnieniu od subscriber_identifier nigdy się nie zmienia, więc to po nim najlepiej wiązać webhook z usługą.
charge_attemptobjectOpcjonalne — obecne tylko na zamówieniach odnowieniowych, którym karta odmówiła. Pola: number (numer próby), reason (surowy powód odmowy od operatora), attempted_at, final (czy to była ostatnia próba), service_ref.
payment_tokenstringOpcjonalne — obecne tylko dla Tpay/PayU (token transakcji w bramce płatności).
ref, url_paramsstring / objectOpcjonalne — parametry afiliacyjne / UTM jeśli były obecne w koszyku.
consentsNoweobjectZgody z chwili złożenia zamówienia. marketing_agreements to lista zaznaczonych zgód (id, kind, text). shop_rules_version to zaakceptowana wersja regulaminu (id, version_date, source_url) albo null. electronic_order_acceptance to treść zgody na zakup produktu elektronicznego z datą wersji albo null (zapisywana od 13.10.2026). To samo pole zwraca API szczegółów zamówienia.

Pola na poziomie order_items[]

PoleTypOpis
product_idintegerID produktu w SalesCRM (stałe między zamówieniami).
name, skustringNazwa produktu i SKU z aktualnej rewizji produktu.
unit_pricedecimal (string)Cena jednostkowa brutto.
vat_ratedecimal (string)Stawka VAT (np. 23, 5, 0).
quantityintegerIlość. Dla pozycji subskrypcyjnych zawsze 1 (patrz niżej).
discountdecimal (string)Rabat na pozycję.
subscriber_identifierstringObecne tylko dla pozycji powiązanych z subskrybentem (rebill subskrypcji albo świeży zakup planu). Pasuje do custom_identifier lub alias rekordu Subscriber w SalesCRM.
subscription_period_daysNoweintegerLiczba dni dostępu, które ta pozycja przyznaje subskrybentowi. Wartość pochodzi z pola Interwał (dni) konfigurowanego w Subskrypcje → Plany subskrypcji przy produkcie. Dostępne tylko dla pozycji z subscriber_identifier. Aplikacje członkowskie używają tej wartości do przedłużenia dostępu (accessExpiresAt = max(now, current) + subscription_period_days).
refstringOpcjonalny identyfikator referencyjny pozycji.
gift_packingbooleanCzy pozycja ma opakowanie prezentowe.
Mixed orders — uwaga. Jedno zamówienie może zawierać równocześnie produkty cykliczne (subskrypcje) i jednorazowe (książki, kursy stand-alone). Pole subscription_period_days jest per pozycja, więc Twoja aplikacja musi liczyć MRR sumując tylko te pozycje gdzie subscriber_identifier jest obecne — nie po całym order.total.

Individual items vs bulk items

Pozycje subskrypcyjne (z subscriber_identifier) są rozbijane na pojedyncze itemy — nawet jeśli klient kupił 3 subskrypcje naraz, dostaniesz 3 rekordy z quantity: 1, każdy z własnym subscriber_identifier i subscription_period_days. Pozycje jednorazowe (bez subskrybenta) są zbiorcze z rzeczywistym quantity.

5. Weryfikacja podpisu (HMAC-SHA256)

SalesCRM podpisuje całe ciało żądania kluczem zabezpieczającym i wysyła wynik w dwóch nagłówkach o tej samej wartości: SHOPLO_HMAC_SHA256 i X-Shoplo-Hmac-Sha256. Jeśli Twoja aplikacja stoi za nginxem albo innym reverse proxy, czytaj X-Shoplo-Hmac-Sha256. Nginx domyślnie odrzuca nagłówki z podkreślnikiem, więc pierwszy z nich może do aplikacji nie dotrzeć. Twoja aplikacja musi:

  1. Przeczytać RAW body żądania (przed parsowaniem JSON).
  2. Policzyć HMAC-SHA256(secret, body) → zwraca hex digest (64 znaki).
  3. Zakodować ten hex jako base64url.
  4. Porównać w sposób timing-safe z wartością z nagłówka.
  5. Odrzucić żądanie (HTTP 403) jeśli nie pasuje.

Przykład weryfikacji w Node.js

import crypto from 'crypto'

function verifySalesCrmWebhook(rawBody, headerValue, secret) {
  const hexHmac = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')

  const expected = Buffer.from(hexHmac)
  const received = Buffer.from(headerValue, 'base64url')

  if (received.length !== expected.length) return false
  return crypto.timingSafeEqual(received, expected)
}

Przykład w PHP

function verifySalesCrmWebhook($rawBody, $headerValue, $secret) {
  $hexHmac = hash_hmac('sha256', $rawBody, $secret);
  $expected = $hexHmac;
  $received = base64_decode(strtr($headerValue, '-_', '+/'));
  return hash_equals($expected, $received);
}

6. Idempotencja i ponowne dostarczanie

Każda odpowiedź inna niż 200/201 trafia do logu nieudanych webhooków (Konfiguracja → Webhooki → Failures). Przy odpowiedzi 5xx SalesCRM dodatkowo ponawia wysyłkę, do 25 razy w coraz dłuższych odstępach (łącznie około 3 tygodni), a każda nieudana próba to kolejny wpis w logu. Przy 4xx ponowień nie ma. Wpis z błędem 4xx możesz wysłać ponownie przyciskiem Ponów w logu. Przy wpisach 5xx zamiast przycisku zobaczysz Ponawiany automatycznie. Jeśli Twoja aplikacja nie odpowie wcale albo przekroczy limit czasu, SalesCRM ponawia tak samo jak przy 5xx, ale takie próby nie trafiają do logu. Zaleca się więc:

Limity wywołania

Od 21 września 2026 SalesCRM pilnuje czasu, rozmiaru i adresu każdego webhooka:

7. Statusy odpowiedzi HTTP

KodInterpretacja SalesCRM
200, 201Sukces. Wpisuje do logów, nie zapisuje failure.
202-499 (poza 200/201)Failure. Zapis do WebhookFailure, ale bez retry. Brak wyjątku w procesie.
500-599Failure z rzuceniem wyjątku WebhookError. Zapis do WebhookFailure.

8. Format alternatywny: Key-value (form-urlencoded)

Starsza forma webhooka (typ Key-value) wysyła application/x-www-form-urlencoded z ograniczonym zestawem pól (głównie order_identifier, email, name, surname, product_id, quantity). Nie zawiera pola subscription_period_days. Używaj wyłącznie do prostych integracji ze starszymi platformami; dla nowych integracji zawsze wybieraj Zaawansowany JSON.

Gotowe! Skonfiguruj webhook w panelu admina — URL Twojej aplikacji + wygenerowany losowo klucz, wybierz status zamówienia, zapisz. Pierwsze zamówienie wpadające w ten status wywoła webhook w ciągu sekundy.
← Wszystkie artykuły Publiczne REST API v2 →