웹훅 개요
웹훅은 이메일 이벤트가 발생하는 즉시 서버로 전달하므로 GET /emails/{id}를 폴링할 필요가 없습니다. SpacePost는 모든 요청에 Standard Webhooks 방식(Svix와 Resend가 쓰는 방식)으로 서명하므로 기존 검증 코드가 그대로 동작합니다.
-
엔드포인트를 만듭니다. JSON 본문의
POST를 받고2xx로 빠르게 응답해야 합니다. -
등록합니다
create-webhook.ts import { SpacePost } from "@spacepost/node";const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);const { data: webhook, error } = await spacepost.webhooks.create({endpoint: "https://acme.com/webhooks/spacepost",events: ["email.delivered", "email.bounced", "email.complained"],});if (error) throw new Error(`${error.name}: ${error.message}`);// Store this secret: you need it to verify signatures. It starts with "whsec_".console.log(webhook.id, webhook.signing_secret);터미널 창 curl -X POST https://api.spacepost.dev/webhooks \-H "Authorization: Bearer $SPACEPOST_API_KEY" -H "Content-Type: application/json" \-d '{"endpoint": "https://acme.com/webhooks/spacepost","events": ["email.delivered", "email.bounced", "email.complained"]}'r = requests.post("https://api.spacepost.dev/webhooks",headers={"Authorization": f"Bearer {os.environ['SPACEPOST_API_KEY']}"},json={"endpoint": "https://acme.com/webhooks/spacepost", "events": ["email.delivered", "email.bounced"]},)print(r.json()["signing_secret"])Response {"object": "webhook","id": "0192e1c4-0000-7000-8000-0000000000aa","endpoint": "https://acme.com/webhooks/spacepost","events": ["email.delivered", "email.bounced", "email.complained"],"status": "enabled","created_at": "2026-10-23T14:00:00.000Z","signing_secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"} -
모든 요청에서 서명을 검증합니다. 서명 검증을 참고하세요.
-
2xx를 반환합니다. 그 외의 응답(리디렉션과 시간 초과 포함)은 재시도됩니다. 재시도를 참고하세요.
요청 형식
섹션 제목: “요청 형식”모든 전달은 Content-Type: application/json과 다음 헤더가 있는 POST입니다.
| 헤더 | 값 |
|---|---|
webhook-id |
이 전달의 고유 ID입니다. 재시도해도 같은 ID이므로 중복 제거에 사용하세요. |
webhook-timestamp |
이 시도를 보낸 시각(Unix 시간, 초 단위)입니다. |
webhook-signature |
v1,<base64 HMAC-SHA256>. 서명이 여러 개이면 공백으로 구분한 목록입니다. |
svix-id, svix-timestamp, svix-signature |
위 세 헤더와 똑같은 복제본이므로 Svix 라이브러리도 그대로 동작합니다. |
본문은 { "type": "...", "created_at": "...", "data": { ... } }입니다. 이벤트 유형을 참고하세요.
웹훅 관리
섹션 제목: “웹훅 관리”POST /webhooks, GET /webhooks, GET /webhooks/{id}, PATCH /webhooks/{id}(endpoint, events, status를 변경할 수 있으며 status는 enabled 또는 disabled), DELETE /webhooks/{id}를 사용합니다. signing_secret은 웹훅을 만들 때와 하나를 조회할 때 반환되며 목록에서는 반환되지 않습니다.
import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);const id = "0192e1c4-0000-7000-8000-0000000000aa";
// Subscribe to more events and temporarily disable delivery.await spacepost.webhooks.update({ id, events: ["email.delivered", "email.bounced", "email.opened"], status: "disabled" });
const { data } = await spacepost.webhooks.list({ limit: 20 });console.log(data?.data.map((w) => `${w.id} ${w.status} ${w.endpoint}`));
await spacepost.webhooks.remove(id);엔드포인트 요구 사항
섹션 제목: “엔드포인트 요구 사항”- 프로덕션에서는
https://를 사용해야 합니다(일반http://는 개발 환경에서만 허용됩니다). - 호스트는 공개 주소로 해석되어야 합니다. localhost와 사설 네트워크는 거부됩니다. 로컬 개발에는 ngrok이나 Cloudflare Tunnel 같은 터널을 사용하세요.
- 15초 안에 응답해야 합니다.
- 리디렉션은 따라가지 않습니다.
이벤트 유형과 페이로드모든 이벤트와 예시 페이로드.
모범 사례멱등하고, 빠르고, 안전한 핸들러.