Aby przetestować webhook Postmark na localhost, uruchom lokalny endpoint, udostępnij jego port poleceniem npx portpreview PORT i wpisz uzyskany adres HTTPS we właściwym Postmark Message Stream. Zabezpiecz trasę przez Basic Authentication albo własny tajny nagłówek, waliduj każdy payload JSON, zapisuj go idempotentnie i szybko zwracaj HTTP 200.
Co Postmark wysyła do webhooka
Po zdarzeniu e-mailowym Postmark wykonuje HTTP POST. Wychodzący Message Stream raportuje dostarczenie, odbicie, otwarcie, kliknięcie, skargę na spam i zmianę subskrypcji, a strumień przychodzący przesyła sparsowaną wiadomość. Pola zależą od zdarzenia, dlatego rozdzielaj je po RecordType i sprawdzaj właściwy schemat. Dostarczenie oznacza przyjęcie przez serwer odbiorcy, nie obecność w skrzynce. Odbicie zawiera m.in. Type, TypeCode, Inactive i CanActivate. Szczegóły opisują oficjalny przegląd webhooków Postmark oraz dokumentacja webhooka odbić.
Zbuduj mały lokalny odbiornik Express
Przykład na porcie 3000 sprawdza Basic Auth przed JSON-em, waliduje minimalną kopertę, trwale zapisuje klucz deduplikacji i dopiero wtedy potwierdza. Funkcje bazy zastąp własną transakcją lub kolejką.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.use('/webhooks/postmark', express.json({ limit: '2mb' }));
function safeEqual(actual, expected) {
const a = Buffer.from(actual);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
function authorized(req) {
const value = req.get('authorization') ?? '';
if (!value.startsWith('Basic ')) return false;
const decoded = Buffer.from(value.slice(6), 'base64').toString('utf8');
const separator = decoded.indexOf(':');
if (separator < 0) return false;
return safeEqual(decoded.slice(0, separator), process.env.POSTMARK_WEBHOOK_USER ?? '') &&
safeEqual(decoded.slice(separator + 1), process.env.POSTMARK_WEBHOOK_PASSWORD ?? '');
}
app.post('/webhooks/postmark', async (req, res) => {
if (!authorized(req)) return res.sendStatus(401);
const event = req.body;
if (typeof event?.RecordType !== 'string' ||
typeof event?.MessageID !== 'string') {
return res.status(400).json({ error: 'Invalid Postmark event' });
}
const deliveryKey = `${event.RecordType}:${event.MessageID}:${event.ID ?? ''}`;
await saveWebhookOnce({ provider: 'postmark', deliveryKey, event });
res.sendStatus(200);
});
app.listen(3000);
Przy wrażliwych akcjach stosuj ścisłą listę pól i limit rozmiaru żądania. Nie loguj całych wiadomości przychodzących — mogą zawierać dane osobowe, linki logowania, załączniki i poufną korespondencję.
Udostępnij localhost pod publicznym adresem HTTPS
- Uruchom odbiornik i sprawdź lokalny port.
- W drugim terminalu wykonaj
npx portpreview 3000. - Do adresu HTTPS dodaj trasę, np.
https://YOUR-TUNNEL.portpreview.dev/webhooks/postmark. - Pozostaw tunel aktywny; po zmianie adresu zaktualizuj Postmark.
Tunel zapewnia osiągalność i zaufany HTTPS, lecz nie uwierzytelnia Postmark. Każdy znający URL może wysłać POST, więc nadal potrzebujesz autoryzacji i walidacji. Przed użyciem danych klientów przeczytaj poradnik bezpieczeństwa tunelu localhost.
Skonfiguruj właściwy webhook Postmark
Dla zdarzeń wychodzących wybierz w Postmark odpowiedni Server i Message Stream, otwórz Webhooks, dodaj URL i włącz tylko obsługiwane wyzwalacze. Dla poczty przychodzącej ustaw osobny inbound URL w Inbound Message Stream. Zespoły konfigurujące hooki kodem mogą użyć oficjalnego Webhooks API: obiekt obsługuje HttpAuth, opcjonalne HttpHeaders i ustawienia wyzwalaczy. X-Postmark-Server-Token służy do wywołań API, a nie jako sekret przesyłany do odbiornika.
Uwierzytelnianie Postmark nie jest podpisem kryptograficznym
Postmark obecnie nie obsługuje weryfikacji podpisu HMAC webhooków. Nie istnieje sekret podpisujący do odtworzenia skrótu surowego body ani nagłówek X-Postmark-Signature. Dokumentowane zabezpieczenia to HTTP Basic Authentication, lista dozwolonych IP i własne nagłówki. Basic Auth lub tajny nagłówek potwierdza znajomość wspólnego sekretu, ale nie wiąże kryptograficznie sekretu z body. HTTPS chroni go w transmisji; później nadal waliduj payload. Zakresy IP pobieraj z aktualnej dokumentacji, bo adres źródłowy może zmieniać się między próbami.
Preferuj pola HttpAuth zamiast danych w URL. Jeśli używasz https://username:[email protected]/path, generuj osobne silne dane, poprawnie koduj znaki i chroń URL przed logami. Nigdy nie używaj tokenu API Server jako hasła webhooka.
Obsługuj dostarczenia i odbicia według typu
Logikę biznesową przenieś poza żądanie HTTP; worker może przetwarzać trwałe zdarzenia idempotentnie:
async function processPostmarkEvent(event) {
switch (event.RecordType) {
case 'Delivery':
await markAcceptedByRecipientServer({
messageId: event.MessageID,
deliveredAt: event.DeliveredAt
});
break;
case 'Bounce':
await recordBounce({
bounceId: String(event.ID),
messageId: event.MessageID,
type: event.Type,
inactive: event.Inactive,
canActivate: event.CanActivate
});
break;
default:
await recordUnhandledPostmarkType(event.RecordType);
}
}
Nie ustalaj trwałej blokady wyłącznie po nazwie pola; stosuj bieżącą klasyfikację Postmark i własną politykę. Skargi i zmiany subskrypcji mają osobne typy. Otwarcia i kliknięcia mogą się powtarzać.
Zaprojektuj obsługę ponowień i duplikatów
Postmark ponawia żądanie bez HTTP 200; odbicia i inbound mają dłuższy harmonogram niż kliknięcia, otwarcia, dostarczenia i zmiany subskrypcji, a 403 zatrzymuje próby. Timeout po commitcie może wywołać poprawny duplikat. Użyj unikalnego stabilnego klucza: Postmark zaleca MessageID, a przy wielu typach dodaj RecordType i identyfikator zdarzenia, np. ID. Zwracaj 200 dopiero po minimalnym trwałym przekazaniu, ale nie czekaj na zewnętrzne API. Wzorce opisuje poradnik ponowień i idempotencji webhooków.
Bezpiecznie testuj prawdziwą obsługę zdarzeń
Najpierw wyślij curl-em syntetyczny POST i sprawdź routing, uwierzytelnienie, walidację oraz zapis. Potem wygeneruj prawdziwe zdarzenia: dostarczenie na własny adres, a odbicie przez udokumentowane narzędzia Postmark, w tym domenę black-hole, jeśli jest dostępna. Zachowaj MessageID, wyślij oczyszczony fixture dwukrotnie i sprawdź pojedynczy efekt. Awarię przejściową symuluj tylko kontrolowanie.
Diagnozuj typowe awarie webhooków Postmark
Żądanie nie dociera do localhost
Sprawdź proces tunelu, pełną trasę URL i zgodność portu. Strona tunelu potwierdza edge, nie trasę POST.
Każde żądanie zwraca 401
Porównaj dane z lokalnym środowiskiem, zrestartuj aplikację i sprawdź, czy proxy nie usuwa Authorization. Nie loguj wartości. Pomaga poradnik błędów webhook 401/403.
Postmark ponawia po udanym przetworzeniu
Sprawdź publiczny status i opóźnienie: Postmark oczekuje dokładnie 200. Nie dopuść, by błąd po zapisie dał 500; zapis musi być idempotentny.
Payload różni się od przykładu
Sprawdź RecordType, wyzwalacz oraz typ strumienia. Odrzucaj brak wymaganych pól, toleruj udokumentowane dodatki i aktualizuj fixtures według referencji.
Lista bezpieczeństwa na produkcję
- Używaj HTTPS i osobnych silnych danych Basic Auth lub tajnego nagłówka.
- Rozdziel tokeny API, dane webhooka i sekrety produkcyjne.
- Po testach rotuj dane i usuwaj stare URL-e.
- Waliduj content type, rozmiar, typ, identyfikatory i pola.
- Redaguj dane e-mail i sekrety w logach.
- Stosuj najmniejsze uprawnienia.
- Monitoruj błędy autoryzacji, opóźnienie, duplikaty i dead letters.
Dobry test lokalny odtwarza produkcję: uwierzytelniony HTTPS, ścisła walidacja, trwała idempotencja, szybkie HTTP 200 i asynchroniczna praca. Szerszy proces znajdziesz w poradniku lokalnego debugowania webhooków.
