Integraciones
Verificación en tu receptor Python
Funciones descargables para GET y HMAC, sin dependencias externas.
Revisado el 28 de septiembre de 2026 · WhatsAut
Qué hace el ejemplo
El archivo utiliza la biblioteca estándar de Python. No inicia un servidor, no guarda mensajes y no envía respuestas a clientes. Integralo en las rutas HTTP de tu aplicación.
Para la prueba inicial usá verify_test_ping con tu token de verificación y la URL exacta guardada en el panel. Para mensajes/estados reales usá la API de verificación de WhatsAut. verify_signature requiere administrar la app Meta y disponer de su App Secret; el verify token no sustituye ese secreto.
Funciones de verificación
Pasá los bytes originales del cuerpo a verify_signature antes de convertirlos a JSON. Una nueva serialización, aunque represente el mismo objeto, puede cambiar la firma.
verify_challenge devuelve el texto exacto cuando la validación es correcta y None cuando hay que rechazar. No confundas None con una respuesta HTTP satisfactoria.
"""Helpers para tu receptor; no inicia un servidor ni procesa mensajes.
verify_test_ping autentica exclusivamente pruebas sintéticas con tu verify token.
Para mensajes/estados usá la API de verificación de WhatsAut, o verify_signature
sólo cuando administrás la app Meta y disponés de su App Secret.
"""
import hashlib
import hmac
import json
import re
from datetime import UTC, datetime
def verify_challenge(
mode: str | None,
token: str | None,
challenge: str | None,
expected_token: str,
) -> str | None:
"""Retorna texto para HTTP 200, o None para rechazar con HTTP 403."""
if mode != "subscribe" or not token or not challenge or not expected_token:
return None
if not hmac.compare_digest(token.encode("utf-8"), expected_token.encode("utf-8")):
return None
return challenge
def verify_signature(raw_body: bytes, signature: str | None, app_secret: str) -> bool:
"""Valida bytes originales. Firma ausente, inválida o secreto vacío: rechazo."""
if not app_secret or signature is None:
return False
if re.fullmatch(r"sha256=[0-9a-fA-F]{64}", signature) is None:
return False
expected = hmac.new(app_secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature[7:].lower())
def verify_test_ping(
raw_body: bytes,
signature: str | None,
verify_token: str,
callback_url: str,
*,
now: datetime | None = None,
) -> bool:
"""Sólo pruebas recientes para la URL exacta del panel; nunca autoriza mensajes.
Usá X-WhatsAut-Test-Signature-256. Deduplificá request_id durante cinco
minutos antes de cualquier efecto adicional; una prueba sólo recibe HTTP 2xx.
"""
if not verify_token or not callback_url or not signature or len(raw_body) > 65536:
return False
if re.fullmatch(r"v1=[0-9a-f]{64}", signature) is None:
return False
material = b"whatsaut.webhook_test.v1\n" + callback_url.encode("utf-8") + b"\n" + raw_body
expected = hmac.new(verify_token.encode("utf-8"), material, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature[3:]):
return False
try:
body: object = json.loads(raw_body)
if not isinstance(body, dict) or set(body) != {"object", "event", "request_id", "sent_at", "test"}:
return False
if body["object"] != "whatsaut_webhook_test" or body["event"] != "whatsaut.webhook_test" or body["test"] is not True:
return False
if not isinstance(body["request_id"], str) or re.fullmatch(r"[A-Za-z0-9_-]{8,80}", body["request_id"]) is None:
return False
if not isinstance(body["sent_at"], str):
return False
sent_at = datetime.fromisoformat(body["sent_at"])
current = now or datetime.now(UTC)
if sent_at.tzinfo is None or current.tzinfo is None:
return False
age = (current - sent_at).total_seconds()
return -30 <= age <= 300
except (ValueError, UnicodeDecodeError, RecursionError, OverflowError):
return False
Cómo conectarlo a tu framework
Ruta GET: leé hub.mode, hub.verify_token y hub.challenge. Si la función devuelve None, respondé 403. En caso contrario, devolvé el texto con 200 y text/plain, sin salto de línea adicional.
Ruta POST: limitá el tamaño durante la lectura, obtené el cuerpo original y rechazá firmas inválidas con 403. Después validá JSON y estructura. Distinguí el ping sintético de eventos reales.
Guardá eventos reales en una cola durable antes del 2xx; procesá y deduplicá en segundo plano. Estas funciones no implementan cola, límites de carga ni protección contra reenvío de un evento ya firmado.
Casos que debe superar
GET con token incorrecto o parámetro faltante: rechazo. Firma ausente, secreto vacío o cuerpo modificado: rechazo. Firma válida sobre los mismos bytes: aceptación.
Los tests del archivo verifican estos casos con secretos ficticios. Eso comprueba el algoritmo, no la conectividad de un endpoint ni la disponibilidad de credenciales para tu integración.