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.

3. Format payloadu (Zaawansowany JSON)

Webhook wysyła POST z nagłówkami Content-Type: application/jsonSHOPLO_HMAC_SHA256 (podpis — 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": []
  }
}

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

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_identifiersubscription_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 nagłówku SHOPLO_HMAC_SHA256. 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

Jeśli Twoja aplikacja zwróci odpowiedź 5xx (lub nie odpowie wcale), SalesCRM zapisze próbę w tabeli WebhookFailurenie będzie automatycznie retryować. Zaleca się więc:

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 →