문서로 건너뛰기
Python

전달 검증하기

`verify_webhook_signature`: 재전송 허용 창을 둔 상수 시간 비교, 그리고 파싱된 이벤트 반환.

요청 핸들러에서

웹훅 URL은 공개되어 있습니다. 인터넷에 있는 누구든 형태만 맞는 JSON을 그 주소로 POST할 수 있으므로, 서명을 확인하지 않고 payload['type']을 읽는 핸들러는 누구에게나 열린 쓰기 API입니다.

webhook_handler.py
import os from fastapi import FastAPI, Request, Responsefrom openemail import WebhookVerificationError, verify_webhook_signature app = FastAPI()  @app.post('/webhooks/openemail')async def webhook(request: Request) -> Response:    try:        event = verify_webhook_signature(            payload=await request.body(),            headers=request.headers,            secret=os.environ['OPENEMAIL_WEBHOOK_SECRET'],            tolerance_seconds=300,        )    except WebhookVerificationError:        return Response('bad signature', status_code=400)     print(event['type'], event['data'])     return Response(status_code=204)

원본 본문을 그대로 전달하십시오. 파싱했다가 다시 직렬화하면 키 순서와 공백이 달라져 서명이 맞지 않습니다. 본문은 bytes든 str이든 됩니다. FastAPI에서는 await request.body(), Django에서는 request.body, Flask에서는 request.get_data()입니다. headers는 각 프레임워크의 request.headers나 평범한 dict 같은 어떤 매핑이든 받으며, X-OpenEmail-Signature는 대소문자와 무관하게 찾아냅니다.

직접 구현한 검사에서 보통 빠지는 두 가지를 이 함수가 처리합니다. MAC을 상수 시간으로 비교하므로 타이밍으로 올바른 접두사를 알아낼 수 없고, tolerance_seconds(따로 지정하지 않으면 5분)보다 앞뒤로 벗어난 오래된 전달을 거부하므로 가로챈 요청을 영원히 재전송할 수 없습니다. tolerance_seconds=0으로 두면 재전송 검사가 꺼집니다. 두 결함 모두 조용합니다. 둘 중 하나를 안고 있는 핸들러도 여러분이 떠올릴 만한 테스트는 전부 통과합니다.

실패하면 언제나 WebhookVerificationError를 발생시킵니다. secret이 비어 있거나, X-OpenEmail-Signature 헤더가 없거나, t=<seconds>,v1=<hex> 형식이 아니거나, 타임스탬프가 허용 창을 벗어났거나, 서명이 맞지 않거나, 본문이 JSON이 아닌 경우입니다. 성공하면 본문을 WebhookPayload로 파싱한 결과를 반환하므로 두 번째 json.loads를 잘못할 일이 없습니다. data에 타입을 붙이려면 결과에 WebhookPayload[EmailOpenedData]처럼 타입을 표기하십시오.

typed_event.py
import os from openemail import verify_webhook_signaturefrom openemail.types import EmailOpenedData, WebhookPayload  def on_open(body: bytes, headers: dict[str, str]) -> None:    event: WebhookPayload[EmailOpenedData] = verify_webhook_signature(        payload=body,        headers=headers,        secret=os.environ['OPENEMAIL_WEBHOOK_SECRET'],    )     print(event['data']['recipient'], event['data']['first'], event['data']['country'])

WebhookVerificationError는 OpenEmailError와 ValueError 둘 다의 하위 클래스입니다. 이 함수는 코루틴이 아닌 평범한 함수이므로, 동기 코드에서든 비동기 코드에서든 await 없이 호출하십시오. 표준 라이브러리의 hmac과 hashlib 외에는 아무것도 필요하지 않습니다.