Actualizaciones asíncronas y webhooks
Solicite el re-scrape de una entidad a la fuente original y reciba el resultado completo por webhook cuando el job termina. Disponible hoy en Panamá.
Qué es y cuándo usarlo
El endpoint GET /v4/panama/entidades/{id}
devuelve el snapshot actual que Panadata tiene de la entidad. Si necesita garantizar que ese snapshot
está al día contra la fuente registral original, dispare una actualización asíncrona con
POST .../entidades/{id}/update.
El backend re-extrae las fuentes y, cuando termina, hace un
POST a su URL de webhook con el resultado
completo firmado.
Tiempo típico de completado: de minutos a una hora según la fuente y la disponibilidad del scraper. No es un endpoint síncrono — no espere la respuesta en la conexión del POST.
Flujo completo
-
POST /v4/panama/entidades/{id}/update?include=<CÓDIGOS>→ recibe202conupdate_request_id. - El backend re-extrae las fuentes y re-serializa la entidad.
- Cuando el job termina con éxito, Panadata hace
POSTa la URL configurada en /webhook_settings con el body firmado. - Si el webhook no llega, caiga a
GET /v4/update_requests/{update_request_id}para recuperar el estado y el resultado.
Configurar URL y firma
Configure su URL de webhook y genere la signing key desde /webhook_settings. Panadata firma cada entrega con HMAC-SHA256 usando esa key. Guárdela inmediatamente — se muestra una sola vez.
La URL debe ser HTTPS, pública y aceptar POST
con Content-Type: application/json.
Responda 2xx para acusar recibo; cualquier otro código se cuenta como fallo.
Disparar una actualización
curl -X POST 'https://api.panadata.net/v4/panama/entidades/4294622/update?include=DAT-CORE' \ --header 'Authorization: Bearer pk_su_llave'
La API responde 202 Accepted con el shape de
UpdateRequestAccepted:
{
"update_request_id": 16,
"status": "pending",
"entity_id": 4294622,
"entity_sid": "PERSONA_JURDICA_Folio_N_1779_M",
"jurisdiction": "panama",
"requested_codes": ["DAT-CORE"],
"estimated_cost": "0.15",
"webhook_url": "https://su-dominio.example.com/panadata/webhooks"
}
Facturación: idéntica al GET de detalle (base $0.01 + suma de códigos solicitados). El cargo se aplica al completarse el job, no al disparar el POST.
Cada POST crea un Update Request nuevo: la API no deduplica por entidad+include.
Si dispara dos solicitudes idénticas en sucesión rápida, recibirá dos
update_request_id distintos
y se le cobrarán ambas al completarse.
Payload del webhook
El body es JSON UTF-8 (sin \uXXXX escaping).
La key data contiene el detalle de la entidad
con el mismo shape que la respuesta del GET de detalle para los códigos resueltos.
POST https://su-dominio.example.com/panadata/webhooks
Content-Type: application/json
X-Panadata-Webhook-Signature: sha256=0e5152ba4b87290c94c1ea79153a67456d1e4ee5ced3f0a86f5082d930bb334f
X-Panadata-Webhook-Timestamp: 1779918984
{
"event": "update_request.completed",
"update_request_id": 16,
"jurisdiction": "panama",
"data": {
"id": 4294622,
"panadata_id": 4294622,
"sid": "PERSONA_JURDICA_Folio_N_1779_M",
"nombre": "ASOCIACIÓN DE SECRETARIAS DEL BANCO NACIONAL DE PANAMÁ (ASOSEBANAL).",
"ruc": null,
"tipo_organizacion": "SOCIEDAD COMÚN",
"status": "VIGENTE",
"vigencia": "PERPETUA",
"fecha_registro": "1982-09-21T05:00:00+00:00",
"folio": "(PERSONA JURÍDICA) Folio Nº 1779 (M)",
"ficha": "1779",
"domicilio": "CIUDAD DE PANAMÁ, DISTRITO PANAMÁ, PROVINCIA PANAMÁ",
"capital": null,
"contacto": null,
"source_updated_at": "2026-05-27 21:56:23.771506+00:00",
"entity_events": [ … ]
}
}
Ejemplo byte-idéntico al delivery real de la UR #16 (2026-05-28), excepto por el array
entity_events, que aquí se muestra
truncado por brevedad (en el delivery real trae sus 17 elementos).
Headers de delivery
| Header | Valor | Notas |
|---|---|---|
| Content-Type | application/json | UTF-8, sin escapado Unicode. |
| X-Panadata-Webhook-Signature | sha256={hex} | HMAC-SHA256 sobre {timestamp}.{body_bytes}. |
| X-Panadata-Webhook-Timestamp | {unix_seconds} | Mismo valor en los 3 intentos de delivery. |
Verificar la firma
Lea los bytes crudos del body (no el JSON parseado), concatene con el timestamp como
{timestamp}.{body_bytes}, calcule
HMAC-SHA256 con su signing key, y compare en tiempo constante con el hex que viene en
X-Panadata-Webhook-Signature.
Rechace si la diferencia entre el timestamp recibido y el actual supera 5 minutos.
# Python
import hmac, hashlib, time
def verify(body_bytes: bytes, timestamp: str, signature: str, signing_key: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
payload = f"{timestamp}.".encode() + body_bytes
expected = "sha256=" + hmac.new(signing_key.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
// Node.js
const crypto = require('crypto');
function verify(bodyBuffer, timestamp, signature, signingKey) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const payload = Buffer.concat([Buffer.from(`${timestamp}.`), bodyBuffer]);
const expected = 'sha256=' + crypto.createHmac('sha256', signingKey).update(payload).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
# Ruby
require "openssl"
def verify(body_bytes, timestamp, signature, signing_key)
return false if (Time.now.to_i - timestamp.to_i).abs > 300
payload = "#{timestamp}.".b + body_bytes
expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", signing_key, payload)
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end
Reintentos de entrega
Panadata intenta entregar hasta 3 veces con 5 s de timeout por intento, sin backoff. Los 3 intentos se ejecutan dentro de la misma invocación Lambda — ventana total ≤ ~15 s. Se reintenta ante cualquier excepción de red o respuesta no 2xx.
Después de 3 fallos, se registra
webhook_status=failed definitivamente. El
Update Request en sí queda status=completed
— el scrape ya tuvo éxito — y el resultado se recupera con
GET /v4/update_requests/{id}.
Idempotencia: el único identificador estable es
update_request_id en el body. El mismo
timestamp y la misma firma se envían en los 3 intentos — no los use como identificador de intento.
Estados ortogonales
| status | webhook_status | Significado |
|---|---|---|
| pending | null | En cola; no hay evento terminal aún. |
| completed | null | Listo; no hubo intento de webhook (sin URL configurada al crear). |
| completed | delivered | Listo; el receiver devolvió 2xx. |
| completed | failed |
Listo; el receiver falló los 3 intentos. Recupere con
GET /v4/update_requests/{id}.
|
| failed | null | Update Request falló; no se enviará webhook. |
Recomendación: combine el webhook con polling de bajo costo a
GET /v4/update_requests/{id}
para los casos donde el webhook nunca llegue (URL caída durante la ventana de 15 s, firewall, etc.).
Cuándo NO se envía un webhook
Solo el evento update_request.completed
produce delivery. Un Update Request en
status=failed
(entidad no encontrada, créditos insuficientes, excepción del scraper) no dispara webhook.
Para observar fallos, consulte
GET /v4/update_requests/{id}: el campo
error describe la causa cuando
status=failed.
Cobertura por jurisdicción
El flujo de escritura + webhook está cableado solo para Panamá hoy. Ecuador y Colombia
ya tienen lectura (GET /v4/{jurisdicción}/entidades)
pero no aceptan POST .../update todavía.
Vea Jurisdicciones para la matriz actualizada.
Anterior: ← Sandbox vs API
Siguiente: Errores →