Verify a delivery and parse it into a :class:`WebhookEvent` in one call (Stripe's ``construct_event`` shape). Raises :class:`~e2a.v1.errors.E2AWebhookSignatureError` on a bad signature, a replay outside tolerance, or an unparseable body. Recommended path — call it from your webhook h
(
raw_body: Union[str, bytes],
header: str,
secret: Secret,
*,
tolerance_seconds: int = 300,
now: Optional[float] = None,
)
| 102 | |
| 103 | |
| 104 | def construct_event( |
| 105 | raw_body: Union[str, bytes], |
| 106 | header: str, |
| 107 | secret: Secret, |
| 108 | *, |
| 109 | tolerance_seconds: int = 300, |
| 110 | now: Optional[float] = None, |
| 111 | ) -> WebhookEvent: |
| 112 | """Verify a delivery and parse it into a :class:`WebhookEvent` in one call |
| 113 | (Stripe's ``construct_event`` shape). Raises |
| 114 | :class:`~e2a.v1.errors.E2AWebhookSignatureError` on a bad signature, a replay |
| 115 | outside tolerance, or an unparseable body. Recommended path — call it from |
| 116 | your webhook handler with the RAW request body. |
| 117 | """ |
| 118 | if not verify_webhook_signature( |
| 119 | raw_body, header, secret, tolerance_seconds=tolerance_seconds, now=now |
| 120 | ): |
| 121 | raise _sig_error("webhook_signature_invalid", "webhook signature verification failed") |
| 122 | |
| 123 | text = raw_body.decode("utf-8") if isinstance(raw_body, bytes) else raw_body |
| 124 | try: |
| 125 | parsed = json.loads(text) |
| 126 | except (ValueError, TypeError): |
| 127 | raise _sig_error("webhook_body_invalid", "webhook body is not valid JSON") |
| 128 | if not isinstance(parsed, dict) or not isinstance(parsed.get("type"), str): |
| 129 | raise _sig_error("webhook_body_invalid", "webhook event is missing a string `type`") |
| 130 | |
| 131 | return WebhookEvent( |
| 132 | type=parsed["type"], |
| 133 | data=parsed.get("data"), |
| 134 | id=parsed.get("id"), |
| 135 | created_at=parsed.get("created_at"), |
| 136 | raw=parsed, |
| 137 | ) |
| 138 | |
| 139 | |
| 140 | def _sig_error(code: str, message: str) -> E2AWebhookSignatureError: |