Webhooks¶
FarmAPI puede notificar un evento compacto por territorio cuando un sync termina.
Callback origin (solo dominio)¶
Configuras un callback como origin, por ejemplo:
https://tu-dominio.com
FarmAPI entregara siempre en:
https://tu-dominio.com/.well-known/farmaapi/webhook
Evento¶
Evento principal:
territory.sync.completed
Ejemplo de payload:
{
"event": "territory.sync.completed",
"event_id": "run-...:ES-MD:territory.sync.completed",
"occurred_at": "2026-02-15T03:15:00Z",
"run_id": "run-...",
"territory_code": "ES-MD",
"status": "success",
"stats": { "fetched": 0, "upserts": 0, "deletes": 0, "published": 0, "rejected": 0 },
"changed": false,
"from_lkg": false
}
Firma (HMAC-SHA256)¶
Headers:
X-Event-IDX-Timestamp(unix seconds)X-Signature(hex sha256)
Firma:
signature = HMAC_SHA256(secret, "{timestamp}.{canonical_json_body}")
Recomendacion: verifica usando el raw body exacto recibido (bytes), sin re-serializar JSON.
Pseudocodigo:
message = "{timestamp}." + raw_body_as_utf8expected = HMAC_SHA256_HEX(secret, message)
import crypto from "node:crypto";
export function verifyFarmaApiWebhook(req, secret) {
const ts = req.header("X-Timestamp") || "";
const sig = req.header("X-Signature") || "";
const rawBody = req.rawBody.toString("utf8"); // necesitas body raw
const msg = `${ts}.${rawBody}`;
const expected = crypto.createHmac("sha256", secret).update(msg).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(sig, "hex"));
}
import hmac, hashlib
def verify(secret: str, ts: str, raw_body: bytes, signature_hex: str) -> bool:
msg = ts.encode("utf-8") + b"." + raw_body
expected = hmac.new(secret.encode("utf-8"), msg, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_hex)
<?php
function verify($secret, $ts, $rawBody, $sigHex) {
$msg = $ts . "." . $rawBody;
$expected = hash_hmac("sha256", $msg, $secret);
return hash_equals($expected, $sigHex);
}