Aby przetestować SendGrid Event Webhook na hoście lokalnym, uruchom program obsługi lokalnie, ujawnij jego port za pomocą npx portpreview PORT, wprowadź wynikowy HTTPS jako adres URL posta SendGrid i weryfikuj każde żądanie za pomocą klucza publicznego Signed Event Webhook przed przetworzeniem jego zdarzeń.
Co wysyła SendGrid Event Webhook
Event Webhook raportuje, co dzieje się po zaakceptowaniu wiadomości przez SendGrid. Zdarzenia związane z dostarczalnością obejmują processed, delivered, deferred, bounce, oraz dropped. Zdarzenia związane z zaangażowaniem obejmują open, click, raporty o spamie i zmiany subskrypcji. Dokładne pola różnią się w zależności od typu zdarzenia, dlatego kieruj głównie na event i traktuj pola opcjonalne jako opcjonalne.
A to JSON array, niekoniecznie jeden obiekt. SendGrid może umieścić kilka zdarzeń w jednym POST. Procedura obsługi, która zakłada req.body.event, po cichu przegapi partię. Oficjalna referencja Event Webhook dokumentuje nazwy i pola zdarzeń, w tym sg_event_id i sg_message_id.
Używaj zdarzeń jako faktów, a nie poleceń. Na przykład zdarzenie delivered może zaktualizować status wiadomości, podczas gdy click może dołączyć rekord zaangażowania. Unikaj sytuacji, w której procedura obsługi click nadpisuje późniejszy stan anulowania subskrypcji tylko dlatego, że żądania dotarły w niewłaściwej kolejności.
1. Create a local endpoint
This Express example deliberately applies a raw-body parser only to the SendGrid route. Signature verification depends on the exact bytes SendGrid signed; parsing and re-serializing JSON can change those bytes.
import express from 'express';
import { EventWebhook, EventWebhookHeader } from '@sendgrid/eventwebhook';
const app = express();
const verifier = new EventWebhook();
const publicKey = verifier.convertPublicKeyToECDSA(
process.env.SENDGRID_WEBHOOK_PUBLIC_KEY,
);
app.post(
'/webhooks/sendgrid',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.get(EventWebhookHeader.SIGNATURE());
const timestamp = req.get(EventWebhookHeader.TIMESTAMP());
if (!signature || !timestamp || !verifier.verifySignature(
publicKey,
req.body,
signature,
timestamp,
)) {
return res.status(403).send('invalid signature');
}
let events;
try {
events = JSON.parse(req.body.toString('utf8'));
} catch {
return res.status(400).send('invalid JSON');
}
if (!Array.isArray(events)) {
return res.status(400).send('expected an event array');
}
await enqueueNewEvents(events);
return res.sendStatus(204);
},
);
app.use(express.json());
app.listen(3000);
Zainstaluj oficjalnego pomocnika za pomocą npm install @sendgrid/eventwebhook. Zamontuj globalny plik express.json() po tej trasie lub jawnie wyklucz tę ścieżkę. Ta sama zasada obowiązuje w Next.js, Fastify, NestJS, funkcjach bezserwerowych i bramkach API: zachowaj oryginalną treść jako bufor łańcuchowy lub bajtowy do czasu pomyślnej weryfikacji. Oficjalne repozytorium węzłów SendGrid ma pasujący przykład signed Event Webhook.
2. Give SendGrid an HTTPS URL
Keep the application running, then open a second terminal:
npx portpreview 3000
PortPreview prints a public HTTPS origin. If it is https://example.portpreview.dev, the complete Post URL is:
https://example.portpreview.dev/webhooks/sendgrid
The path must match the route exactly. Keep the tunnel process alive while testing. A tunnel forwards traffic; it does not replace your local server, so connection failures usually mean the app is stopped, listening on another port, or bound in a way the tunnel cannot reach.
3. Skonfiguruj Event Webhook w SendGrid
- W interfejsie użytkownika SendGrid, open Ustawienia > Ustawienia poczty.
- Uw ustawieniach webhooka, open Event Webhooks i wybierz Utwórz nowy webhook.
- EWłącz, dodaj adres URL PortPreview jako adres URL wpisu i wybierz tylko te akcje, których potrzebuje Twoja aplikacja.
- Under Funkcje zabezpieczeń, włącz Signed Event Webhook.
- Zapisz webhooka, ponownie open jego ustawienia, skopiuj wygenerowany publiczny klucz weryfikacyjny i zapisz go jako
SENDGRID_WEBHOOK_PUBLIC_KEY. - Użyj Test Your Integration, a następnie wyślij prawdziwą wiadomość, aby sprawdzić typy zdarzeń, które mają znaczenie.
SendGrid zauważono, że test wysyła przykładowe zdarzenia, a nie dane z rzeczywistej wysyłki poczty. Zapisz przed testowaniem weryfikacji signature: para kluczy jest generowana podczas zapisywania konfiguracji Signed Event Webhook.
Jak działa weryfikacja podpisanego webhooka SendGrid
Signed Event Webhook wykorzystuje ECDSA. SendGrid przechowuje klucz prywatny i wyświetla odpowiedni publiczny klucz weryfikacyjny. Każda dostawa obejmuje X-Twilio-Email-Event-Webhook-Signature i X-Twilio-Email-Event-Webhook-Timestamp. Weryfikacja obejmuje timestamp połączony z nieprzetworzonymi bajtami ładunku i skrótem SHA-256; signature jest zakodowany w Base64. Oficjalny pomocnik obsługuje konwersję klucza publicznego, dekodowanie signature, haszowanie i weryfikację ECDSA.
Jest to weryfikacja asymetryczna: wyświetlana wartość jest kluczem publicznym, a nie sekretem HMAC. Nie uruchamiaj ładunku poprzez JSON.stringify(), nie usuwaj białych znaków, nie dodawaj znaku nowej linii ani nie sprawdzaj pojedynczych elementów tablicy. Najpierw sprawdź pełne bajty żądania, a następnie przeanalizuj tablicę. Algorytm i nagłówki można znaleźć w dokumentacji SendGrid dotyczącej funkcji bezpieczeństwa.
A ważny signature stwierdza, że podpisane bajty pochodzą od posiadacza klucza prywatnego SendGrid i nie zostały zmienione. Nie czyni przetwarzania zdarzeń idempotentnym, nie zezwala na dowolne działania ani nie udowadnia, że zdarzenie jest nowe. To osobne elementy sterujące.
Make batch processing idempotent
SendGrid retries failed POSTs, and networks can lose a successful response. Therefore, duplicate delivery is normal. Use each event's sg_event_id as the primary deduplication key, with a unique database constraint. If your product combines multiple SendGrid accounts or environments, namespace the key by provider and account or environment.
async function enqueueNewEvents(events) {
for (const event of events) {
await db.transaction(async (tx) => {
const inserted = await tx.webhookReceipts.insertIfAbsent({
provider: 'sendgrid',
eventId: event.sg_event_id,
receivedAt: new Date(),
});
if (!inserted) return;
await tx.jobs.enqueue({
type: 'process-sendgrid-event',
payload: event,
});
});
}
}
Wkładka paragonu i trwała kolejka powinny zostać zatwierdzone razem. Zwróć tylko 2xx po tym, jak partia będzie trwale accepted. Jeśli jedno zdarzenie zakończy się niepowodzeniem po zatwierdzeniu innych, odpowiedź inna niż 2xx może spowodować zwrócenie całego żądania; deduplikacja pozwala następnej próbie pominąć zdarzenia już accepted i bezpiecznie kontynuować. Nie używaj Set w pamięci w środowisku produkcyjnym, ponieważ ponowne uruchomienie go kasuje i wiele instancji nie udostępnia go. Szerszy przewodnik dotyczący ponawiania prób i idempotencji webhooka obejmuje trwałe wzorce.
Zrozumienie ponownych prób przed wybraniem kodów stanu
AZgodnie z dokumentacją SendGrid Event Webhook, odpowiedź 2xx oznacza, że POST zakończyło się sukcesem. Odpowiedź inna niż 2xx powoduje ponowne próby w rosnących odstępach czasu do 24 godzin po zdarzeniu; jest to okno kroczące dla każdego nowego zdarzenia zakończonego niepowodzeniem. To zachowanie oznacza, że trwała awaria signature może również generować powtarzające się próby, a zwrócenie 2xx dla zdarzenia, którego nigdy nie zapisałeś, powoduje jego utratę.
- 2xx: cała partia została uwierzytelniona i trwale accepted, czyli każde zdarzenie jest już znane.
- 4xx: nieprawidłowe lub nieuwierzytelnione dane wejściowe. Rejestruj tylko bezpieczną diagnostykę; spodziewaj się ogólnego zachowania ponawiania próby SendGrid innego niż 2xx.
- 5xx: przejściowa awaria bazy danych, kolejki lub aplikacji, którą należy ponowić.
Staraj się, aby ścieżka żądania była krótka: weryfikuj, sprawdzaj poprawność zewnętrznego kształtu, atomowo deduplikuj i umieszczaj w kolejce, a następnie odpowiadaj. Wykonuj aktualizacje analizy poczty e-mail, synchronizację CRM i powiadomienia w plikach roboczych.
Rozwiązywanie problemów lokalnych SendGrid webhooków
signature jest zawsze nieprawidłowe
Najczęstszą przyczyną jest oprogramowanie pośrednie JSON zużywające treść przed weryfikacją. Potwierdź, że weryfikator otrzymał oryginalny Buffer, łącznie ze wszystkimi początkowymi i końcowymi białymi znakami. Następnie sprawdź, czy klucz publiczny należy do tej dokładnej konfiguracji Event Webhook i czy oba nagłówki Twilio docierają do aplikacji w niezmienionej postaci. Uruchom ponownie proces lokalny po zmianie jego środowiska.
Integracja testowa powiodła się, ale nie pojawiają się rzeczywiste zdarzenia
Sprawdź, czy element webhook jest włączony i czy wybrano żądane działania. Otwory wymagają śledzenia open, a click wymagają śledzenia click. Pamiętaj też, że żądanie testowe zawiera przykłady; użyj rzeczywistego wysyłania, aby sprawdzić pola i sekwencję przypominającą produkcję.
Punkt końcowy zwraca 404 lub 502
W przypadku 404 porównaj skonfigurowaną ścieżkę z /webhooks/sendgrid. W przypadku błędów bramy upewnij się, że aplikacja lokalna działa na tym samym porcie przekazanym do PortPreview. Jeśli nadejdą żądania, ale zwrócą 500, sprawdź lokalne dzienniki i tymczasowo zmniejsz procedurę obsługi do weryfikacji i trwałego przechwytywania.
Ezdarzenia są zduplikowane lub nie działają
To rzeczywistość systemu dostarczania, a nie dowód na to, że tunel powiela ruch. Deduplikuj za pomocą sg_event_id, tam, gdzie to możliwe, spraw, aby przejścia stanu były monotoniczne i przechowuj czas zdarzenia oddzielnie od czasu odbioru. Użyj lokalnego procesu debugowania webhooka, aby odizolować błędy transportu, uwierzytelniania i logiki biznesowej.
Lista kontrolna bezpieczeństwa do użytku lokalnego i produkcyjnego
- Użyj HTTPS i weryfikuj każdy signature przed analizowaniem lub rejestrowaniem szczegółów zdarzenia.
- Zachowaj publiczny klucz weryfikacyjny w konfiguracji, aby można go było bezproblemowo zaktualizować po zmianie klucza webhooka.
- Akceptuj tylko POST, ograniczaj rozmiar żądania, sprawdź, czy analizowana wartość jest tablicą i zezwalaj tylko na obsługiwane nazwy zdarzeń.
- Nie umieszczaj PII w kategoriach SendGrid ani unikalnych argumentach; Odniesienie SendGrid wyraźnie ostrzega, że te pola są przechowywane i nie są traktowane jako informacje umożliwiające identyfikację.
- Nie ujawniaj sesji administratora, konsoli debugowania ani niepowiązanych tras lokalnych przez to samo tymczasowe źródło.
- Nie rejestruj adresów odbiorców, ładunków, signature ani wartości środowiska, chyba że jest to konieczne i odpowiednio zredagowane.
- Zastąp tymczasowy adres URL tunelu stabilnym produkcyjnym punktem końcowym HTTPS po przetestowaniu i wyłącz przestarzałe konfiguracje webhooka.
SendGrid może również używać OAuth 2.0 do zabezpieczenia Event Webhook, samodzielnie lub razem z signature. Jeśli Twoje wdrożenie wymaga kontroli cyklu życia nośnika token, postępuj zgodnie z oficjalnym przewodnikiem bezpieczeństwa, zamiast wymyślać wymianę token. Weryfikacja podpisu pozostaje cenna, ponieważ wiąże dokładne bajty timestamp i ładunku.
A test akceptacyjny gotowości do produkcji
- Wyślij podpisane żądanie testowe i potwierdź odpowiedź 2xx.
- Zmień jeden bajt ładunku i potwierdź 403 bez zapisu do bazy danych.
- Odtwórz ponownie identyczne ważne żądanie i potwierdź, że nie ma duplikatów zadań ani działań biznesowych.
- Wyślij obiekt JSON zamiast tablicy i potwierdź kontrolowane 400.
- Zatrzymaj na chwilę bazę danych, potwierdź 5xx, przywróć ją i sprawdź, czy ponowna próba to accepted raz.
- Wyślij prawdziwy e-mail i potwierdź, że wybrane zdarzenia dostawy i zaangażowania podążają tą samą ścieżką.
Po pomyślnym zakończeniu tych kontroli przenieś punkt końcowy do środowiska produkcyjnego bez zmiany logiki weryfikacji i idempotencji. W przypadku głębszych trybów awarii kryptograficznych zapoznaj się z przewodnikiem weryfikacji webhook signature.
