Webhooks
Con los webhooks, tu aplicación se entera en el momento en que algo ocurre en Edugoverna — se registra una decisión de consentimiento, entra una solicitud ARCO por el portal, falla el envío de un mensaje — sin necesidad de consultar la API en un ciclo de polling. En esta guía verás cómo se firman las entregas, qué forma tiene el sobre de cada evento y el catálogo completo de eventos disponibles.
Cambio en el esquema de firma. La firma HMAC-SHA-256 ya no se calcula
sobre el cuerpo a secas: ahora se calcula sobre la cadena
"{timestamp}.{rawBody}", donde timestamp es el valor de la cabecera
x-edugoverna-timestamp (segundos Unix) y rawBody son los bytes exactos del
cuerpo recibido. Si tu integración verificaba la firma del cuerpo solo, debes
actualizarla: las firmas antiguas ya no coinciden. El detalle está en
Verificación de la firma.
Registrar un endpoint
Los endpoints de webhook se administran desde la Consola (Integraciones → Webhooks) o mediante la API de gestión de webhooks. Al crear un endpoint eliges un nombre, la URL de destino (solo https://), los eventos a los que te suscribes y, opcionalmente, un secreto propio. Si omites el secreto, el servidor genera uno con prefijo whsec_ y lo devuelve una única vez en la respuesta de creación.
Los webhooks requieren que la organización tenga habilitado el módulo integrations en su licencia. Sin ese módulo, crear un endpoint o activarlo responde 409 INTEGRATIONS_MODULE_REQUIRED, y la Consola lo avisa en Integraciones → Webhooks. Si el módulo se retira después de registrar endpoints, los eventos no se entregan y cada omisión queda en la auditoría de la organización (outbound_webhook_event · skipped_module_disabled). Un partner puede comprobar de antemano si los webhooks salen de una organización con webhooks.available en la conexión.
Cabeceras de cada entrega
Cada entrega es un POST con content-type: application/json a la URL del endpoint, acompañado de tres cabeceras:
- Nombre
x-edugoverna-signature- Tipo
- string
- Descripción
Firma HMAC-SHA-256 en hexadecimal, calculada como
HMAC(secret, "{timestamp}.{rawBody}"). El nombre de esta cabecera es configurable por endpoint (signatureHeader); el valor por defecto esx-edugoverna-signature.
- Nombre
x-edugoverna-event- Tipo
- string
- Descripción
El tipo de evento entregado, por ejemplo
consent.decision.recorded.
- Nombre
x-edugoverna-timestamp- Tipo
- string
- Descripción
El instante en que se firmó la solicitud, en segundos Unix. Úsalo para reconstruir la cadena firmada y para rechazar entregas antiguas.
Verificación de la firma
Para comprobar que una entrega proviene efectivamente de Edugoverna:
- Lee la cabecera
x-edugoverna-timestampy el cuerpo crudo de la solicitud (los bytes exactos recibidos — no re-serialices el JSON ya parseado). - Construye la cadena firmada como
${timestamp}.${rawBody}, con un punto literal entre el timestamp y el cuerpo. - Calcula
HMAC-SHA-256(secret, cadenaFirmada)en hexadecimal y compáralo con la cabecera de firma usando una comparación de tiempo constante. - Rechaza la entrega si
|ahoraEnSegundos - timestamp| > 300(5 minutos), para protegerte de la repetición de una solicitud capturada.
import crypto from "node:crypto"
const TOLERANCE_SECONDS = 300 // 5 minutos
function verifyWebhook({ rawBody, headers, secret }) {
const timestamp = headers["x-edugoverna-timestamp"]
const signature = headers["x-edugoverna-signature"]
if (!timestamp || !signature) {
return false
}
// Rechaza entregas demasiado antiguas (protección anti-replay).
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp))
if (!Number.isFinite(age) || age > TOLERANCE_SECONDS) {
return false
}
// Recalcula la firma sobre `${timestamp}.${rawBody}`.
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex")
// Comparación de tiempo constante.
const a = Buffer.from(expected, "hex")
const b = Buffer.from(signature, "hex")
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
El mismo esquema para los conectores ARCO
Si además recibes acciones de derechos como conector externo (external_post, configurado por el colegio en su portal ARCO), la firma es idéntica: cada despacho llega con x-edugoverna-timestamp y la cabecera de firma que el colegio configuró para el conector, calculada como HMAC(secret, "{timestamp}.{rawBody}") con el secreto del conector. El cuerpo trae en callback la URL a la que respondes y el esquema (signatureScheme, timestampHeader, toleranceSeconds). Tu callback se firma igual, en sentido inverso: envía x-edugoverna-timestamp y x-edugoverna-signature sobre "{timestamp}.{rawBody}"; sin timestamp responde 401 ARCO_ACTION_CALLBACK_TIMESTAMP_REQUIRED, fuera de la ventana de 300 s 401 ARCO_ACTION_CALLBACK_STALE, y con firma incorrecta 401 ARCO_ACTION_CALLBACK_INVALID_SIGNATURE. Un solo verificador sirve para ambos.
Guarda el secreto del webhook con el mismo cuidado que una credencial de API: quien lo conozca puede fabricar entregas válidas. Si sospechas que se filtró, rota el secreto — la rotación genera uno nuevo y lo devuelve una sola vez.
El sobre del evento
Toda entrega comparte el mismo sobre. Los campos específicos del evento viajan dentro de data.
- Nombre
event- Tipo
- string
- Descripción
El tipo de evento, uno de los del catálogo. Coincide con la cabecera
x-edugoverna-event.
- Nombre
sourceType- Tipo
- string
- Descripción
La clase de recurso que originó el evento:
consent_request,consent_delivery,rights_request,security_incidentoincident_notification.
- Nombre
sourceId- Tipo
- string
- Descripción
El identificador del recurso de origen. Úsalo para consultar el estado actual por la API si lo necesitas.
- Nombre
occurredAt- Tipo
- string
- Descripción
Fecha y hora ISO 8601 en que se construyó la entrega.
- Nombre
context- Tipo
- object
- Descripción
Solo en algunos eventos. Hechos del evento que no viven en el recurso de origen: en
consent.recaptured,reasonysupersededConsentRequestId; enconsent.invalidated,invalidationReason; enrights_request.sla_*,stageyeffectiveDueAt. Escalares planos, nunca datos personales.
- Nombre
data- Tipo
- object | null
- Descripción
La proyección del recurso de origen en el momento de la entrega. Su forma depende de
sourceType— ver los payloads por sourceType.
{
"event": "consent.decision.recorded",
"sourceType": "consent_request",
"sourceId": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"occurredAt": "2026-08-25T14:03:22.511Z",
"data": {
"id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"status": "granted"
// ... proyección completa según sourceType
}
}
Catálogo de eventos
Estos son los 29 tipos de evento del catálogo, agrupados igual que en el selector de la Consola. La suscripción es por lista explícita o mediante el comodín ["*"].
Consentimiento
Eventos sobre el ciclo de vida de una solicitud de consentimiento. sourceType es consent_request. Ver Consentimientos.
- Nombre
consent.requested- Descripción
Se creó una solicitud de consentimiento (desde la Consola, una campaña o la API) y sus entregas quedaron encoladas.
- Nombre
consent.decision.recorded- Descripción
Se registró una decisión (otorgada o denegada), también cuando el titular acota o vuelve a otorgar desde el portal de transparencia. En campañas sobre padrones compartidos, este y todos los eventos de consentimiento se emiten para cada parte — ver la proyección para partners.
- Nombre
consent.revoked- Descripción
El titular o un operador revocó un consentimiento otorgado.
context.revocationSourcedice por dónde:portal(el enlace del consentimiento),transparency_self_service(el portal de transparencia),staff(la Consola o la API de organización) opartner(la Partner API) oregistrar(el registrador externo transmitió un retiro que la familia comunicó al colegio; ver Consentimiento declarado). Una misma solicitud puede revocarse más de una vez si el titular vuelve a otorgar entre medio: cada retiro llega como su propio evento.
- Nombre
consent.expired- Descripción
Una solicitud pendiente pasó su
expiresAtsin decisión y fue marcada como expirada por el barrido programado.
- Nombre
consent.invalidated- Descripción
Una solicitud fue anulada:
data.statusesinvalidatedycontext.invalidationReasondice por qué."campaign_closed": se cerró su campaña antes de la decisión."superseded_by_campaign": una campaña nueva reemplazó la solicitud pendiente."version_superseded": se publicó una versión de la actividad que exige volver a consentir, y la anulada puede haber estado otorgada."superseded_by_titular": el titular, ya con edad para decidir, respondió una solicitud nueva y su decisión reemplazó el «sí» que había dado el apoderado."registration_rejected": el colegio rechazó un registro público ya otorgado. Las solicitudes anuladas no pasan después aexpired, así que este es su evento terminal.
- Nombre
consent.recaptured- Descripción
La plataforma anuló una solicitud y emitió una nueva sin que nadie la pidiera: una pendiente se redirigió a quien decide hoy porque el titular cruzó la edad en que eso cambia (
context.reason = "age_threshold", según laageTransitionPolicyde la versión; ocurre en el próximo recordatorio o reenvío, o al intentar firmarla), o se publicó una versión del aviso que invalida las anteriores ("notice_version").dataes la solicitud nueva;context.supersededConsentRequestIdes la anterior, cuando la hay. No se emiteconsent.requestedpor estas solicitudes.
- Nombre
consent.student_notified- Descripción
En una campaña compartida con aviso al estudiante activo, EduGoverna procesó el aviso al estudiante después de que su apoderado otorgó.
context.outcomeessent(se envió a su correo registrado) ono_contact(el estudiante no tiene correo; no se envió nada).
{
"event": "consent.decision.recorded",
"sourceType": "consent_request",
"sourceId": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"occurredAt": "2026-08-25T14:03:22.511Z",
"data": {
"id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"status": "granted",
"decisionActorType": "guardian",
"requestedAt": "2026-08-20T12:00:00.000Z",
"expiresAt": "2026-09-20T12:00:00.000Z",
"deliveries": [
{
"channel": "email",
"status": "opened",
"destinationMasked": "m…a@example.cl"
}
],
"latestDecision": {
"decision": "granted",
"decidedAt": "2026-08-25T14:03:21.000Z",
"captureMethod": "portal_link_otp"
}
// ... campos adicionales de la proyección
}
}
Envíos de consentimiento
Eventos sobre los mensajes individuales (email, WhatsApp) con que se solicita el consentimiento.
- Nombre
consent.delivery.updated- Descripción
Una entrega cambió de estado (
queued,sent,delivered,opened,failed, …), ya sea por confirmación del proveedor de mensajería o por una actualización manual.sourceTypeesconsent_deliveryy la entrega usa el nuevo estado comooccurrenceKey, así que cada transición llega una vez.
- Nombre
consent.delivery.opened- Descripción
El titular o apoderado abrió por primera vez el enlace del portal de consentimiento.
sourceTypeesconsent_request: el payload es la solicitud completa, no la fila de la entrega.
- Nombre
envelopes.job.completed- Descripción
Terminó un despacho de sobres por lote.
sourceTypeesconsent_envelope_jobydatatrae el estado y los conteos; los resultados por fila se leen en el job.
- Nombre
consent.delivery.failed- Descripción
El proveedor reportó una falla definitiva para esa entrega. En WhatsApp,
failureReasontambién puede indicar que el canal dejó de estar disponible (cuenta restringida, sin fondeo, mensaje no aprobado o tope de gasto alcanzado): son fallas permanentes que no se reintentan hasta que la organización las resuelva y use «Reenviar».sourceTypeesconsent_delivery; se emite además delconsent.delivery.updatedcorrespondiente.
{
"event": "consent.delivery.failed",
"sourceType": "consent_delivery",
"sourceId": "7c1f00aa-2e43-4b7e-9f14-3d2e9a7c51d0",
"occurredAt": "2026-08-25T09:12:44.008Z",
"data": {
"id": "7c1f00aa-2e43-4b7e-9f14-3d2e9a7c51d0",
"consentRequestId": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"channel": "whatsapp",
"destinationMasked": "+569••••1234",
"status": "failed",
"failedAt": "2026-08-25T09:12:40.000Z",
"failureReason": "Provider delivery failed."
// ... campos adicionales de la fila de entrega
}
}
Padrones compartidos
Eventos de la relación con los colegios y registradores que te comparten datos. Llegan sólo a tu espacio de trabajo y su data es una proyección propia ("projection": "partner_lifecycle"): ids, estados, fechas y nombres de organizaciones y grupos, nunca personas. Ver Registradores y solicitudes.
- Nombre
share.granted- Descripción
Un colegio o registrador te compartió un grupo.
sourceTypeesregistry_shareydatatrae el grupo (groupName,groupKind,course), el dueño (owner) ymemberCount.
- Nombre
share.revoked- Descripción
El dueño dejó de compartirte el grupo, directamente o al revocar la conexión.
context.reasondice por qué cuando se sabe. Los consentimientos ya otorgados siguen vigentes (son evidencia). Lo que pierdes es el padrón.
- Nombre
share.members.changed- Descripción
La composición de un grupo cambió en una carga del registrador.
context.addedCountycontext.removedCounttraen los conteos; relee el grupo consincepara saber quiénes.
- Nombre
share_request.decided- Descripción
El colegio aprobó o rechazó tu solicitud de cursos.
sourceTypeesshare_request;data.shareIdsson los shares creados ydata.externalRef, el que mandaste al pedirlos.
- Nombre
registrar.completed- Descripción
El registrador que invitaste cargó su primer curso: la invitación se cumplió.
sourceTypeesregistrar_invite.dataes la proyecciónpartner_lifecyclede la invitación:inviteId,status,externalRef(tu correlativo),registryOrganizationId(el registro creado),schoolNameyschoolRbd(el colegio que declaró el registrador;schoolNamecae alschoolNameHintde la invitación si aún no hay registro),acceptedAt,processingActivityId(la actividad de la invitación,nullsi no llevaba consentimiento declarado) yshareIds(los grupos que ya te comparte).
Conexiones
- Nombre
connection.accepted- Descripción
Un colegio aceptó la conexión (tu solicitud, incluida la hecha por RBD).
sourceTypeespartner_connection;data.organizationtrae suorganizationId. Si la conexión nació de una solicitud tuya,data.connectionRequestIdydata.externalRefla identifican; ambos sonnullcuando fue el colegio quien te invitó.
- Nombre
connection.revoked- Descripción
El colegio revocó la conexión. También recibes un
share.revokedpor cada grupo que estaba compartido.
- Nombre
dpa.activated- Descripción
Entró en vigor un contrato de encargo (Art. 15 bis) con el colegio, también al aceptarse una conexión.
sourceTypeesdpa_contract.
- Nombre
dpa.expired- Descripción
Venció el contrato (
effectiveTopasó). Desde ese momento la API responde403 PARTNER_DPA_NOT_ACTIVEsobre ese colegio.
Derechos ARCO
Eventos sobre solicitudes de derechos. sourceType es rights_request y data trae la proyección completa de la solicitud tal como la ve la Consola. Ver Derechos ARCO.
- Nombre
rights_request.created- Descripción
Entró una solicitud nueva, por ejemplo a través del portal ARCO público.
- Nombre
rights_request.updated- Descripción
La solicitud recibió actividad nueva, como un mensaje de seguimiento del titular por el portal.
- Nombre
rights_request.document.added- Descripción
El titular adjuntó uno o más documentos a la solicitud (al crearla o en un seguimiento posterior).
- Nombre
rights_request.export_ready- Descripción
El paquete de exportación aprobado terminó de construirse y quedó disponible para descarga. Se entrega con
sourceType: "rights_request"y unoccurrenceKeyúnico por construcción del paquete.
- Nombre
rights_request.closed- Descripción
La solicitud fue cerrada (respondida o denegada) con su certificado de cierre.
{
"event": "rights_request.created",
"sourceType": "rights_request",
"sourceId": "9e2a41d7-6c3b-4f0e-8a17-b5d4c2e8f901",
"occurredAt": "2026-08-25T16:40:02.113Z",
"data": {
"id": "9e2a41d7-6c3b-4f0e-8a17-b5d4c2e8f901",
"requestType": "access",
"status": "received"
// ... proyección completa de la solicitud
}
}
Plazos ARCO
Eventos sobre el plazo legal de respuesta de una solicitud de derechos. Se emiten en el mismo barrido que envía el recordatorio por correo al equipo responsable; sourceType es rights_request y context trae stage y effectiveDueAt (la fecha vigente, con prórroga si la hubo).
- Nombre
rights_request.sla_due_soon- Descripción
Una solicitud abierta entró en la ventana previa a su vencimiento.
- Nombre
rights_request.sla_overdue- Descripción
Una solicitud abierta superó su fecha de vencimiento sin cierre. Se repite cada tres días hábiles mientras siga abierta, cada vez con su propio
occurrenceKey(overdue,overdue_repeat:1,overdue_repeat:2…) y concontext.repeatIndexycontext.escalateden las repeticiones.
Incidentes
Eventos sobre incidentes de seguridad y sus notificaciones a titulares.
- Nombre
security.incident.created- Descripción
Se registró un incidente de seguridad y se encolaron las notificaciones a los titulares afectados.
sourceTypeessecurity_incident.
- Nombre
security.incident.notification.updated- Descripción
La notificación de incidente a un titular cambió de estado según el proveedor de mensajería.
sourceTypeesincident_notificationy el nuevo estado viaja comooccurrenceKey.
{
"event": "security.incident.created",
"sourceType": "security_incident",
"sourceId": "4f8b2c6d-1a0e-4d3f-9c7b-2e5a8d1f6b09",
"occurredAt": "2026-08-25T18:05:37.902Z",
"data": {
"id": "4f8b2c6d-1a0e-4d3f-9c7b-2e5a8d1f6b09"
// ... fila del incidente
}
}
Payloads por sourceType
El contenido de data es la proyección del recurso de origen en el momento en que se construye la entrega (no en el momento del hecho: si el recurso cambió entre medio, verás su estado más reciente).
- Nombre
consent_request- Tipo
- object
- Descripción
La proyección completa de la solicitud de consentimiento: la fila principal (
id,status,decisionActorType,processingActivityVersionId,targetSubjectId,requestedAt,expiresAt, …) másdeliveries[],latestDecision(con su desglose de opciones y evidencia) ylatestEvent. Si la solicitud pertenece a una campaña se agregancampaignId,campaignMode,campaignPublicSubjectFlow,campaignIdentityVerificationMode,campaignStatus,campaignContactEntryIdycampaignContactEntry.
- Nombre
consent_delivery- Tipo
- object
- Descripción
La fila de la entrega:
id,consentRequestId,channel,destinationMasked,status,deliveryProvider,providerMessageId, las marcas de tiempoqueuedAt/sentAt/deliveredAt/openedAt/failedAtyfailureReason.
- Nombre
rights_request- Tipo
- object
- Descripción
La proyección completa de la solicitud de derechos, incluida su actividad asociada (documentos, mensajes, cierre), tal como la entrega la API de la Consola.
- Nombre
security_incident- Tipo
- object
- Descripción
La fila del incidente de seguridad.
- Nombre
incident_notification- Tipo
- object
- Descripción
La fila de la notificación de incidente a un titular, con su estado de entrega.
data puede llegar como null si el recurso de origen fue eliminado antes de
que la entrega se construyera, o si el evento no tiene una proyección aprobada
para la audiencia del endpoint (ver la sección siguiente). Trata data: null
como una señal de "consulta el recurso por la API usando sourceId".
Proyección para partners (shared_partner)
En una campaña sobre un padrón compartido, la campaña vive en la organización del partner mientras la solicitud de consentimiento vive en la del sostenedor de los datos. Todo evento de consentimiento de esa solicitud (consent.requested, consent.decision.recorded, consent.revoked, consent.expired, consent.invalidated, consent.recaptured y los tres de envío) se emite para ambas partes con el mismo occurrenceKey:
- La copia del sostenedor lleva la proyección completa descrita arriba.
- La copia que reciben los endpoints de la organización del partner lleva una proyección reducida, construida como lista blanca campo a campo. Se identifica a sí misma con
"projection": "shared_partner"para que nunca se confunda con una proyección completa a la que "le faltan" campos.
En la copia del partner, sourceType es siempre consent_request, también en los eventos de envío (el partner no tiene la fila de la entrega). Esos eventos agregan en context el id de la entrega (consentDeliveryId, el mismo de portalAccessLinks), deliveryChannel, deliveryStatus y, si falló, failureCode (destination_missing, dispatch_blocked o provider_failed), nunca el texto del proveedor ni el destino.
En una campaña segmentada por RUT el partner no recibe nada de una solicitud hasta que haya una decisión: un consent.requested le diría qué RUT están en el padrón, que es justo lo que ese modo protege.
La proyección reducida se limita a lo que el partner ya puede leer por su propio endpoint de sujetos compartidos: no incluye RUT (ni enmascarado), contactos en ninguna forma, el nombre del titular, el id interno del sujeto en la organización dueña, ni el contenido de la evidencia o texto libre de operadores. Sí trae el id y el checksum del manifiesto sellado (latestDecision.evidence), que no nombran a nadie y se validan en /api/public/evidence/verify. latestDecision.decisionAuthority indica el rol que decidió (titular, apoderado), nunca quién.
latestDecision.captureMethod dice cómo se capturó la decisión: portal_link_otp (la familia, con el enlace y el código de un solo uso) o registrar_attestation, el consentimiento declarado por el establecimiento mediante un registrador externo. Esta segunda no es una firma con código: su evidencia es una declaración jurada más la custodia del colegio, y tu integración debería mostrarla distinta. Los retiros que el registrador transmite llegan como consent.revoked con context.revocationSource: "registrar".
{
"event": "consent.decision.recorded",
"sourceType": "consent_request",
"sourceId": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"occurredAt": "2026-08-25T14:03:22.511Z",
"data": {
"projection": "shared_partner",
"consentRequestId": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"dataOwnerOrganizationId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"registryShareId": "a3bb189e-8bf9-3888-9912-ace4e6543002",
"campaignId": "16fd2706-8baf-433b-82eb-8c7fada847da",
"campaignStatus": "active",
"campaignMode": "per_subject",
"campaignOrigin": "operator",
"campaignContactEntryId": "886313e1-3b8a-5372-9b90-0c9aee199e5d",
"partnerExternalId": "par_04421",
"processingActivityVersionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"subject": {
"referenceCode": "EST-04421",
"subjectType": "student",
"requestedForAgeBand": "under_14"
},
"status": "granted",
"decisionActorType": "guardian",
"requestedAt": "2026-08-20T12:00:00.000Z",
"expiresAt": "2026-09-20T12:00:00.000Z",
"updatedAt": "2026-08-25T14:03:21.000Z",
"latestDecision": {
"id": "0b8e4c2a-5d1f-4a6b-8c3e-7f9d2a1b6c4e",
"decision": "granted",
"decisionAuthority": "guardian",
"captureMethod": "portal_link_otp",
"decidedAt": "2026-08-25T14:03:21.000Z",
"effectiveFrom": "2026-08-25T14:03:21.000Z",
"effectiveTo": null,
"revokedAt": null,
"evidence": {
"id": "…:manifest",
"checksum": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b"
}
}
}
}
campaignOrigin dice quién originó la campaña: operator (la lanzaste tú) o registrar_attestation (los consentimientos que un registrador externo declaró firmados; no se enviaron mensajes). Es null si la solicitud no pertenece a una campaña. campaignContactEntryId es el id del sobre y partnerExternalId tu correlativo, tal como lo mandaste al despachar. Cualquier otro sourceType en una copia de audiencia partner no tiene proyección aprobada, y por diseño no se divulga nada: data llega null.
Suscripción comodín
El valor almacenado ["*"] suscribe el endpoint a todos los eventos, incluidos los que se agreguen al catálogo en el futuro. El comodín debe ir solo: enabledEvents acepta ["*"] o una lista de tipos explícitos, nunca una mezcla. Si omites enabledEvents al crear el endpoint — o envías una lista vacía — el valor almacenado es ["*"].
Requisitos y garantías de entrega
- Solo HTTPS. La URL del endpoint debe usar
https://y no puede apuntar a hosts locales, direcciones privadas, link-local ni a la IP de metadatos de nube. - Secreto de una sola vista. Si no envías un secreto al crear el endpoint, el servidor genera uno (
whsec_…) y lo devuelve solo en esa respuesta. Puedes rotarlo cuando quieras; la rotación también lo devuelve una única vez. - Reintentos. Una entrega se considera exitosa cuando tu endpoint responde con un estado 2xx. Si responde otra cosa o la conexión falla, el intento queda registrado como fallido y la cola vuelve a intentarlo; cada intento queda en el historial de entregas con su
attemptNumber. Los destinos que ya recibieron con éxito una combinación (evento, origen, ocurrencia) no la vuelven a recibir en los reintentos. - Idempotencia. Cada transición distinta de un mismo recurso viaja con su propia
occurrenceKeyinterna, pero un reintento puede hacer que veas el mismo evento más de una vez. Deduplica por la tuplaevent+sourceId+ el contenido relevante dedata(por ejemplo, elstatusen los eventos de entrega) y responde 2xx rápido — procesa en segundo plano si tu trabajo es lento. - Historial auditable. Cada intento queda en el registro de entregas del endpoint, consultable por la API de gestión: evento, origen, número de intento, estado y resumen del error (en un intento fallido, el resumen incluye el código con que respondió tu servidor). De la respuesta de tu servidor solo se conserva el código de estado, nunca el cuerpo.
- Módulo requerido. El despacho exige el módulo
integrationsactivo en la licencia de la organización. - Endpoints por conexión. Un endpoint puede quedar asociado a una conexión de partner mediante
organizationPartnerConnectionId, para que el destino quede documentado junto a la relación que lo justifica.