메인 콘텐츠로 건너뛰기
수신 주소는 인터넷에 열려 있어서 누구나 그 주소로 요청을 보낼 수 있습니다. MeterEngine은 모든 웹훅에 수신 주소의 서명 비밀값으로 만든 서명을 붙이므로, 수신 서버는 서명이 맞는 요청만 처리해야 합니다. 서명 방식은 Standard Webhooks 사양의 HMAC-SHA256 서명(v1)을 따릅니다. MeterEngine은 SDK를 배포하지 않으므로(MeterEngine 소개) 이 페이지의 검증 코드를 그대로 옮겨 쓰거나, 아래 절차대로 다른 언어로 구현합니다. HMAC-SHA256과 base64를 쓸 수 있으면 어떤 언어든 됩니다. 서명 비밀값을 받는 방법은 웹훅 받기 시작하기를 보세요.

서명에 쓰는 헤더

다시 보낼 때마다 webhook-timestamp와 webhook-signature는 새로 만들어지고, webhook-id와 본문은 처음과 같습니다.

검증 절차

1

본문을 받은 바이트 그대로 보관한다

서명은 MeterEngine이 보낸 본문의 바이트 그대로에 대해 만듭니다. JSON으로 파싱한 뒤 다시 문자열로 만들면 키 순서와 공백이 바뀌어 서명이 맞지 않습니다.
2

서명 헤더를 읽는다

webhook-id, webhook-timestamp, webhook-signature 가운데 하나라도 없으면 거절합니다.
3

서명 시각을 확인한다

webhook-timestamp가 숫자인지 보고, 수신 서버의 지금 시각과 차이가 허용 범위 안인지 확인합니다. 허용 범위는 수신 서버가 정합니다. 이 페이지의 예시는 300초(5분)입니다. 시도마다 서명 시각이 새로 정해지므로 재전송된 웹훅도 이 검사를 통과합니다. 수신 서버의 시계는 NTP로 맞춰 두세요.
4

서명 키를 만든다

서명 비밀값에서 앞의 whsec_를 떼고 나머지를 base64로 디코드한 바이트가 HMAC 키입니다. 비밀값 문자열을 그대로 키로 쓰면 맞지 않습니다.
5

기대하는 서명을 계산한다

아래 문자열을 HMAC-SHA256으로 서명하고 결과를 base64로 씁니다.
서명할 내용
세 값은 마침표(.) 하나로 이어 붙입니다.
6

헤더의 서명과 비교한다

webhook-signature 값을 공백으로 나누고, v1,로 시작하는 것마다 v1, 뒤의 값을 기대하는 서명과 비교합니다. 하나라도 같으면 통과입니다. 비교는 상수 시간 비교 함수(Node의 timingSafeEqual 같은 것)로 합니다. 일반 문자열 비교는 응답 시간으로 서명을 추측하는 공격에 약합니다.
7

통과한 뒤에만 본문을 처리한다

서명이 맞으면 본문을 JSON으로 파싱하고, 본문의 environment가 이 수신 주소를 등록한 업무 환경과 같은지 확인한 뒤 처리합니다. 같은 webhook-id를 이미 처리했으면 처리하지 않고 2xx로 응답합니다. 중복을 거르는 방법은 전송, 재전송, 중복 처리를 보세요.

검증 코드

Node 18 이상의 ES 모듈(.mjs 파일이나 package.json의 "type": "module")에서 별도 패키지 없이 동작합니다. 비밀값은 환경 변수 METERENGINE_WEBHOOK_SECRET에 넣었다고 가정합니다.
프레임워크의 JSON 본문 파서(예: Express의 express.json())를 거친 뒤에는 원문이 사라집니다. 웹훅 경로에서는 본문을 바이트 그대로 받는 설정(예: express.raw({ type: "application/json" }))을 쓰고, 서명을 확인한 다음 직접 파싱하세요. 앞단의 프록시나 게이트웨이가 본문을 다시 쓰는지도 확인하세요.

검증에 실패했을 때

서명이 맞지 않으면 2xx가 아닌 상태 코드(예: 400)로 응답하고 본문을 처리하지 마세요. MeterEngine은 2xx가 아닌 응답을 실패로 보고 정해진 일정에 따라 다시 보냅니다. 비밀값을 잘못 넣어 모든 시도가 실패했다면, 비밀값을 고친 뒤 ‘발송 실패’로 끝난 전송을 수동으로 다시 보냅니다. 재전송 일정과 수동 재전송은 전송, 재전송, 중복 처리를 보세요.
  • 본문을 파싱한 뒤 다시 직렬화하지 않았는지 확인하세요. MeterEngine이 보내는 본문은 아래처럼 키 순서가 문서의 설명 순서와 다르고 :와 , 뒤에 공백이 있습니다. 이 모양을 흉내 내지 말고 받은 바이트를 그대로 쓰세요.
  • whsec_를 떼고 base64로 디코드한 바이트를 키로 썼는지 확인하세요.
  • 서명할 내용의 순서가 webhook-id, webhook-timestamp, 본문이고 마침표로 이었는지 확인하세요.
  • 비교할 때 헤더 값의 v1,를 떼었는지 확인하세요.
  • 수신 주소마다 비밀값이 다릅니다. 테스트 환경 수신 주소의 비밀값으로 실제 환경 웹훅을 검증하고 있지 않은지 확인하세요.
수신 서버의 시계가 어긋나 있으면 정상 요청도 허용 범위를 벗어납니다. 서버 시각을 NTP로 맞추세요. 서명 시각은 시도마다 새로 정해지므로 오래 기다린 재전송 때문에 거절되지는 않습니다.
받은 요청의 헤더 값과 본문 원문을 파일로 남겨 두었다면, 아래 명령으로 기대하는 서명을 계산해 webhook-signature의 v1, 뒤 값과 비교할 수 있습니다(openssl, xxd, base64가 있는 셸).
body.json은 받은 본문을 한 바이트도 바꾸지 않고 저장한 파일이어야 합니다. 편집기로 열어 저장하면 끝에 줄바꿈이 붙어 값이 달라질 수 있습니다.