Per què vam treure l'intermediari
La primera versió passava per un orquestrador de fluxos. Meta envia el webhook allà, l'eina el processa i acaba escrivint a la base de dades. És el que fa tothom i té sentit mentre no calgui res estrany.
Ens vam encallar a la primera pantalla. Per donar d'alta un webhook, Meta fa una crida de verificació i espera que li tornis exactament el paràmetre `hub.challenge` que t'acaba d'enviar, en text pla i sense res més. Aquella versió de l'eina no ho feia bé, i no hi havia manera de convèncer-la.
Vam decidir apuntar Meta directament a una edge function. El resultat: una peça menys al camí, sense una eina intermèdia que pugui caure, sense un pla de pagament més i sense dos llocs on mirar quan alguna cosa no arriba.
Les tres coses que has de fer bé
1. La signatura, en temps constant
Meta signa cada petició amb HMAC SHA-256 fent servir el secret de l'aplicació. Es calcula la signatura del cos cru —abans de parsejar-lo com a JSON, perquè qualsevol reserialització canvia els bytes— i es compara amb la capçalera.
I la comparació no es fa amb `===`. Es recorren tots els caràcters acumulant diferències amb XOR, i es respon al final. Comparar cadenes amb l'operador normal surt tan bon punt troba el primer caràcter diferent, i aquell temps de resposta és informació: amb prou intents, permet endevinar la signatura caràcter a caràcter.
És una precaució barata i és de les que no es noten mai fins que és tard.
2. Idempotència pel wamid
Meta reenvia. Si el teu endpoint triga o retorna un error, el mateix missatge tornarà a arribar, potser diverses vegades. Cada missatge porta un identificador únic —el wamid— i és la clau que evita duplicats.
Sense això, un pic de latència teva es converteix en tres missatges idèntics a la conversa del client.
3. Els estats arriben desordenats
Meta t'avisa quan un missatge teu s'envia, s'entrega i es llegeix. Aquests avisos no arriben en ordre. És perfectament normal rebre el «llegit» abans que el «entregat».
Si els apliques tal com arriben, un missatge que ja constava com a llegit torna a constar com a entregat, i el client veu que has desfet el temps.
La solució que fem servir no és codi: és la condició de la consulta. L'actualització a «entregat» només s'aplica si l'estat actual és nul o «enviat». La de «llegit», si és nul, «enviat» o «entregat». La de «fallat» sempre.
Dit d'una altra manera: sent només si l'estat és nul; delivered només si és nul o sent; read només si és nul, sent o delivered.
Posant el guard dins del `where`, la base de dades resol la cursa ella sola. Si ho fessis llegint l'estat, decidint i escrivint, dos esdeveniments simultanis es poden trepitjar entre la lectura i l'escriptura. Aquí no hi ha finestra.
Aquesta és la part que més ens agrada d'aquesta peça, i val per a qualsevol cosa que rebi esdeveniments d'un tercer.
El lead es crea sol
Si arriba un missatge d'un número que no coneixem, es crea el lead abans de desar res: estat inicial, origen WhatsApp i el telèfon normalitzat a només dígits. L'activitat s'escriu sempre, hi hagi lead previ o no.
Els números arriben en formats diferents segons d'on vinguin. Normalitzar-los a l'entrada és el que evita tenir el mateix client tres vegades a la base de dades.
La trampa que ens va costar una tarda
El secret de l'aplicació s'ha de rotar a Meta i al teu servidor en el mateix moment. Si els canvies amb deu minuts de diferència, durant aquells deu minuts totes les peticions fallen la verificació de signatura i el teu endpoint respon 401.
I des de fora sembla que no passi res: Meta no t'avisa, els missatges simplement no arriben, i el registre de l'aplicació diu que s'han enviat. Vam trigar a mirar-ho perquè no hi havia cap error a cap pantalla.
Si un dia et deixen d'arribar missatges sense cap motiu aparent, comprova el secret abans que res.
