웹훅
활성화 이벤트를 서버에서 실시간으로 받고, SmsGrab-Signature 헤더를 검증하고, 재시도, 테스트 전송, 자동 비활성화를 이해하세요.
웹훅은 이벤트가 일어나는 즉시 내 서버로 보내 주므로 계속 물어볼 필요가 없습니다. 웹훅 하나는 API 키 하나에 속하며, 그 키로 구매한 번호의 이벤트와 계정의 잔액 부족 경고를 받습니다.
이벤트
| 이벤트 | 시점 |
|---|---|
activation.created |
번호를 구매함 |
activation.code_received |
새 SMS 문자마다, 지금까지의 모든 메시지와 함께 |
activation.completed |
내가 완료했거나, 코드 수신 후 시간이 끝남 |
activation.cancelled |
코드 전에 취소됨, 환불 완료 |
activation.expired |
제시간에 코드 없음, 환불 완료 |
activation.refunded |
청구 금액이 환불될 때마다 |
balance.low |
잔액이 설정한 기준 아래로 내려감 |
webhook.test |
내가 시작한 테스트 전송 |
웹훅 만들기
계정 > 개발자 화면에서 키를 열고 URL 주소, 받을 이벤트, 선택 설명을 입력해 웹훅을 추가합니다. 키 하나에 웹훅을 최대 3개까지 둘 수 있습니다.
URL 주소 조건:
https://형태로 시작해야 합니다.- IP 주소가 아닌 호스트 이름을 써야 합니다.
- 공개 주소를 가리켜야 합니다. 사설 네트워크, localhost, 이와 비슷한 범위는 거부됩니다.
whsec_ 형태로 시작하는 서명 시크릿이 발급됩니다. API 키와 마찬가지로 한 번만 표시됩니다. 나중에 바꿀 수도 있으며, 이때도 비밀번호가 필요합니다.
전송 형식
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: SmsGrab-Webhooks/1.0
SmsGrab-Event: activation.code_received
SmsGrab-Delivery: 9f7d6c1e-2b44-4c1a-8f0e-6a3d2c1b0e9f
SmsGrab-Signature: t=1790503200,v1=3b9a0c…
{ "id": "evt_4e1f…", "type": "activation.code_received", "created_at": "2026-09-27T10:06:12.004Z",
"api_version": "2026-09-27", "data": { "activation": { "id": "5c1d0c2e-…", "status": "CODE_RECEIVED" } } }
서명 검증하기
SmsGrab-Signature 헤더에는 타임스탬프 t 값과 v1 값이 들어 있습니다. v1 값은 t, 마침표, 요청 원본 본문을 이어 붙인 것을 내 시크릿을 키로 하여 HMAC-SHA256 방식으로 계산한 값입니다. 타임스탬프가 내 시계와 300초 넘게 차이 나는 전송은 거부하고, 서명은 상수 시간으로 비교하세요.
[$t, $v1] = (function (string $h): array {
parse_str(str_replace(',', '&', $h), $p);
return [(int) ($p['t'] ?? 0), (string) ($p['v1'] ?? '')];
})($_SERVER['HTTP_SMSGRAB_SIGNATURE'] ?? '');
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $t.'.'.$body, getenv('SMSGRAB_WEBHOOK_SECRET'));
$ok = abs(time() - $t) <= 300 && hash_equals($expected, $v1);
import hmac, hashlib, time
def verify(header: str, body: bytes, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return abs(time.time() - t) <= 300 and hmac.compare_digest(expected, parts.get("v1", ""))
import crypto from 'node:crypto';
export function verify(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t ?? 0);
const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex');
const given = Buffer.from(parts.v1 ?? '', 'utf8');
return Math.abs(Date.now() / 1000 - t) <= 300 && given.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), given);
}
JSON 파싱 전에 반드시 받은 그대로의 원본 본문으로 검증하세요.
빠르게 응답하기
10초 안에 아무 2xx 상태로 응답하고, 오래 걸리는 작업은 대기열 등에서 나중에 처리하세요. 리디렉션은 따라가지 않습니다.
재시도
실패한 전송은 1분, 5분, 15분, 1시간, 3시간, 6시간, 12시간, 24시간 후에 재시도됩니다. 재시도에는 같은 이벤트 id 값이 담기므로, 처리한 ID 값을 저장해 두고 중복은 건너뛰세요. 한 활성화의 이벤트는 순서대로 도착합니다.
자동 비활성화
410 Gone상태로 응답하면 웹훅이 바로 비활성화됩니다.- 최소 24시간 동안 50번 연속으로 실패하면 웹훅이 비활성화되고 알림이 전송됩니다. 엔드포인트를 고친 뒤 웹훅을 다시 활성화하세요.
- 키를 폐기하면 해당 키의 웹훅도 비활성화됩니다.
테스트와 재전송
웹훅 페이지에서 webhook.test 이벤트를 보내면 상태 코드, 응답 시간, 오류를 바로 확인할 수 있습니다. 전송 목록에는 모든 전송이 30일 동안 보관되며, 어느 것이든 다시 보낼 수 있습니다.
이 문서가 도움이 되었나요?
감사합니다! 도움이 되었다니 기쁩니다.