핵심 개념
중복 접수 막기
타임아웃 뒤 재시도가 같은 메시지를 두 번 보내지 않도록 Idempotency-Key 를 쓰는 법.
발송 요청은 실제로 돈이 나가고 사람에게 도착하는 동작입니다. 네트워크가 끊겨 응답을 못 받았을 때 그냥 다시 보내면, 서버는 이미 접수했는데 요청자만 모르는 상황이 되어 같은 메시지가 두 번 발송됩니다.
Idempotency-Key 헤더는 그것을 막습니다. 발송 3종 (알림톡 · 브랜드 메시지 · 문자)에서 쓸 수 있습니다.
동작
- 처음 보는 키면 평소처럼 접수하고
202를 돌려줍니다. - 같은 키로 다시 부르면 두 번째 접수를 만들지 않고
8627로 거절합니다. 이미 접수됐다는 뜻입니다.
키를 정하는 법
핵심은 같은 의도의 요청에 같은 키가 붙는 것입니다. 요청마다 새 UUID 를 만들면 재시도에도 새 키가 붙어 아무것도 막지 못합니다.
- 업무상 사건에서 뽑으세요.
order-10231-shipped처럼 「어떤 주문의 어떤 알림인가」를 담으면 자연히 같은 값이 나옵니다. - 재시도 사이에 값이 유지되어야 합니다. 키를 함수 안에서 만들지 말고, 재시도 루프 바깥에서 한 번 정하세요.
- 내용이 달라지면 키도 달라져야 합니다. 같은 주문이라도 다른 메시지를 보낸다면 다른 키를 쓰세요 — 안 그러면 두 번째 발송이 8627 로 막힙니다.
재시도 예시
// 주문 하나당 키 하나. 재시도해도 같은 키를 씁니다.
const idempotencyKey = `order-${orderId}-shipped`;
async function sendWithRetry() {
for (let attempt = 0; attempt < 3; attempt += 1) {
const response = await fetch(url, {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body,
});
const envelope = await response.json();
// 이미 접수된 요청입니다 — 재시도가 성공한 것으로 봐야 합니다.
if (envelope.code === 8627) return { alreadyAccepted: true };
if (envelope.success) return envelope.data;
// 5xx 등 일시적 실패만 다시 시도합니다.
if (response.status < 500) throw new Error(envelope.message);
}
throw new Error("접수에 실패했습니다.");
}응답을 못 받았다면
타임아웃이 나서 접수 여부를 모를 때, 가장 안전한 순서는 이렇습니다 — 같은 키로 한 번 더 부릅니다. 8627 이 오면 첫 요청이 살아 있는 것이고, 202 가 오면 첫 요청은 도달하지 않았던 것입니다. 어느 쪽이든 발송은 정확히 한 번입니다.