Especificación del feed de empleos para ATS

Formatos aceptados (XML Joonow, Indeed y LinkedIn), cada campo con ejemplo, reglas de lectura y cierre y buenas prácticas para el feed de empleos de tu ATS.

19 min de lectura Versión en Markdown

En este artículo
  1. Resumen
  2. Formatos aceptados
  3. XML Joonow
  4. XML de Indeed
  5. XML de LinkedIn
  6. Cómo leemos el feed
  7. Ciclo de vida del empleo
  8. Válvula de seguridad
  9. Empresa confidencial
  10. Salario
  11. Ubicación e idioma
  12. Postulación
  13. Calidad y revisión
  14. Buenas prácticas para que el empleo se vea bien
  15. Postulaciones por webhook
  16. Cómo solicitar la integración

Esta es la especificación pública del feed de empleos de Joonow para sistemas de reclutamiento (ATS) y sitios de empleo. Con ella armas el feed, lo revisas en el validador y solicitas la integración sin hablar con nadie. El programa es gratuito: mira qué gana tu ATS.

Resumen

  • Un archivo XML en una URL pública, de preferencia con https://, con todos los empleos abiertos de tus clientes.
  • Cada empleo necesita id estable, título, descripción, URL de postulación y empresa.
  • Recomendado: el correo de quien responde por el empleo (email). No aparece en el sitio y hace que el empleo suba en el orden (por qué incluirlo).
  • Recomendado: el sitio y el logo de la empresa (companyurl, companylogo); la descripción (companydescription) es opcional. El logo aparece en los empleos, en la búsqueda y en el directorio, y los tres ayudan a armar la página de la empresa (por qué incluirlos).
  • Leemos el feed cada 6 horas (la agenda se acuerda en la aprobación), con GET condicional (ETag y Last-Modified).
  • El empleo que sale del archivo se cierra en la segunda lectura seguida sin él.
  • La postulación ocurre en tu sitio: el botón lleva a la URL del empleo con utm_source=joonow. O, con las postulaciones por webhook activadas, el candidato se postula en Joonow y la postulación llega firmada a tu sistema.
  • Antes de solicitar, valida en el validador: muestra los errores de cada empleo y no guarda nada.

Formatos aceptados

Formato Cuándo usarlo Elemento de cada empleo
XML Joonow (recomendado) Vas a crear el feed ahora o ya generas el XML de Indeed. <job>
XML de Indeed Tu ATS ya publica el XML estándar de Indeed (requisitionid, sourcename, remotetype). <job>
XML de LinkedIn Tu ATS ya genera el XML de empleos de LinkedIn (partnerJobId, applyUrl). <job>

Los tres usan la raíz <source> con una lista de <job>. Los nombres de los elementos no distinguen mayúsculas y el texto puede venir dentro de CDATA.

XML Joonow

Es el XML que muchos ATS ya generan para Indeed, con algunos campos más (vigencia, modalidad híbrida y nivel). Descarga el XML de ejemplo y el esquema XSD.

<?xml version="1.0" encoding="UTF-8"?>
<source>
  <publisher>Nombre de tu ATS</publisher>
  <publisherurl>https://www.tu-ats.com</publisherurl>
  <lastBuildDate>2026-10-30T09:00:00Z</lastBuildDate>
  <job>
    <referencenumber>ACME-4821</referencenumber>
    <title><![CDATA[Analista de Soporte Junior]]></title>
    <company><![CDATA[Acme Logística]]></company>
    <email>reclutamiento@acme.example</email>
    <companyurl>https://www.acme.example/</companyurl>
    <companylogo>https://www.acme.example/img/logo-acme-256.png</companylogo>
    <companydescription><![CDATA[Operador logístico con almacenes en Pernambuco y Bahía.]]></companydescription>
    <url><![CDATA[https://acme.tu-ats.com/empleos/4821]]></url>
    <date>2026-10-28T13:00:00Z</date>
    <expirationdate>2026-12-15T23:59:59Z</expirationdate>
    <city><![CDATA[Guadalajara]]></city>
    <state>Jalisco</state>
    <country>MX</country>
    <description><![CDATA[<p>Atención a clientes...</p><h3>Requisitos</h3><ul><li>...</li></ul>]]></description>
    <salary><![CDATA[MX$ 15.000 a MX$ 18.000 al mes]]></salary>
    <jobtype>fulltime</jobtype>
    <category>Professional</category>
    <remotetype>Hybrid remote</remotetype>
  </job>
</source>

Campos del empleo

Elemento Obligatorio Tipo Ejemplo Cómo lo usa Joonow
referencenumber Sí Texto, hasta 200 caracteres ACME-4821 Identifica el empleo de una lectura a otra. Tiene que ser único y estable: si cambia, se vuelve otro empleo. Sin él, usamos la URL de postulación.
title Sí Texto, hasta 200 caracteres Analista de Soporte Junior Título del empleo, en la búsqueda y en la página. Un título todo en MAYÚSCULAS pasa a minúsculas normales (las siglas se mantienen).
description Sí HTML simple, hasta 50 KB <p>…</p><ul><li>…</li></ul> Descripción del empleo. Solo quedan p, br, ul, ol, li, strong, b, em, i, u, h2, h3, h4, a, blockquote y hr; lo demás (script, estilo, imagen) se quita.
url Sí URL https:// https://acme.tu-ats.com/empleos/4821 Destino del botón "Postularme", con utm_source=joonow. Tiene que abrir el propio empleo.
company Sí Texto Acme Logística Nombre de la empresa: crea la página de la empresa con todos sus empleos. Vacío se vuelve "Confidencial".
city Recomendado Texto Guadalajara Ciudad: filtros, búsqueda por ubicación y páginas de ciudad.
state Recomendado Sigla o nombre Jalisco Estado o región. En Brasil, la sigla (UF) o el nombre completo.
country Recomendado Código ISO de 2 letras o nombre MX País. Vacío usa el país predeterminado del feed, definido en la aprobación.
date Recomendado Fecha (ISO 8601 o RFC 1123) 2026-10-28T13:00:00Z Fecha de publicación: orden, "publicado hace X días" y antigüedad del empleo. Sin ella, usamos la fecha de la lectura; una fecha futura pasa a hoy.
email Recomendado Correo, hasta 254 caracteres reclutamiento@acme.example Correo profesional de quien responde por el empleo. No aparece en el sitio. El empleo suma puntos en el orden y, si recibe una denuncia, el equipo de Joonow tiene con quién hablar (por qué incluirlo). Si no es válido, se ignora sin rechazar el empleo.
companyurl Recomendado URL absoluta https://www.acme.example/ Sitio de la propia empresa (no el del ATS, de una red social ni de una página de empleos). Se vuelve el enlace de la página de la empresa y ayuda a encontrar el logo y las redes (por qué incluirlos).
companylogo Recomendado URL absoluta de imagen https://www.acme.example/img/logo.png Logo de la empresa en PNG, JPEG o WebP, de 128 px o más (cuadrado es mejor). Joonow descarga una copia y la sirve desde su propio sitio.
companydescription Opcional Texto o HTML simple, hasta 4.000 caracteres Operador logístico con almacenes en el Nordeste. Lo que hace la empresa, para su página. Repite el mismo texto en todos los empleos de la empresa.
expirationdate Opcional Fecha (ISO 8601) 2026-12-15T23:59:59Z Vigencia: después de ella, el empleo se cierra. El empleo que llega ya vencido se rechaza.
salary Opcional Texto con valor o rango MX$ 15.000 a MX$ 18.000 al mes Salario, solo cuando la empresa lo informa. Reconocemos moneda (R$, US$, €), rango y periodo (hora, día, semana, mes, año).
jobtype Opcional Código fulltime Tipo de contrato (valores abajo).
category Opcional Código Professional Nivel del empleo (valores abajo). Otros valores se ignoran.
isRemote Opcional Yes o No Yes Yes = 100% remoto; No = presencial.
remotetype Opcional Texto Hybrid remote Fully remote = 100% remoto; Hybrid remote = híbrido. Vale cuando no viene isRemote.

Los elementos publisher, publisherurl y lastBuildDate de la raíz son opcionales: ayudan a identificar el archivo, pero no cambian los empleos.

Por qué incluir el correo del responsable

El email (en el XML de LinkedIn, posterEmail) es el correo profesional de quien responde por el empleo: la persona reclutadora o el buzón del equipo de RR. HH. de la empresa. No es obligatorio, pero vale la pena:

  • El empleo sube en la búsqueda. El correo suma puntos en la nota de calidad del anuncio, como el salario y la descripción completa, y esa nota desempata el orden entre los empleos que coinciden con la búsqueda: con el correo, el empleo pasa delante de los equivalentes sin él. Aparecer antes suele traer más visitas y más postulaciones.
  • Hay con quién hablar antes de retirar el empleo. Cuando el empleo recibe una denuncia de algo que se puede corregir (un dato equivocado, un enlace cambiado), el equipo de Joonow escribe a ese correo antes de decidir si lo retira.
  • El correo se queda con el equipo de Joonow. Nunca aparece en el empleo, en las búsquedas, en la API ni a los candidatos, no se vende ni se usa para publicidad y se borra 30 días después de que el empleo sale del aire (política de privacidad).

Envía una dirección completa por empleo (reclutamiento@empresa.com). Los buzones "no responder" no sirven. Un correo no válido se ignora sin rechazar el empleo (entra sin el punto), y el validador muestra cuántos empleos traen el correo.

Por qué incluir el sitio, el logo y la descripción de la empresa

El companyurl, el companylogo y el companydescription arman la página de cada empresa cliente en Joonow, con el mismo valor en todos sus empleos:

  • El logo aparece en todas partes: en la tarjeta del empleo, en la búsqueda, en el empleo abierto y en el directorio de empresas, y va en el JSON-LD del empleo para Google.
  • Ayudan a armar la página de la empresa: el sitio se vuelve el enlace de la página después de verificado, las redes sociales salen de él, y la descripción entra, junto con el propio sitio de la empresa, en el texto breve que escribimos sobre ella (nunca publicamos la descripción tal como vino).
  • Nada de esto cambia el empleo: sitio, logo y descripción son de la empresa. Cambiar el logo no vuelve a grabar los empleos, y un valor no válido se ignora sin rechazar el empleo.

El logo se descarga una vez y lo sirve Joonow (nunca cargamos imágenes de tu servidor en la página). Si la empresa reclama su página, lo que ella escriba vale en lugar de lo que vino en el feed. El validador muestra cuántos empleos traen el logo y el sitio.

Valores aceptados

jobtype (tipo de contrato): fulltime (tiempo completo), parttime (medio tiempo), contract (prestador de servicios, freelancer), temporary (temporal), internship (prácticas) y volunteer (voluntariado). También aceptamos las etiquetas en portugués de Brasil: Efetivo – CLT, Estatutário, Estágio, Prestador de Serviços - PJ, Freelancer, Autônomo, Cooperado, Temporário, Tempo Determinado e Intermitente.

category (nivel): los códigos Apprentice (aprendiz), Intern (prácticas), Entry (inicio de carrera), Operational (operativo), Professional (profesional), Management (coordinación y gerencia) y Executive (dirección), o las etiquetas en portugués Aprendiz, Estágio, Trainee, Operacional, Profissional, Supervisão/Coordenação y Gerencial e Executivos.

Modalidad de trabajo: isRemote con Yes o No, o remotetype con Fully remote, Hybrid remote u On-site. "COVID-19" (remoto solo temporalmente) queda sin modalidad.

Qué no usar

Nuestro lector entiende el elemento priority por compatibilidad, pero no forma parte del programa: los empleos patrocinados no se acuerdan por el feed. phone y los campos internos del ATS se ignoran. Además del correo del responsable del empleo, no envíes datos personales en el feed (nunca datos de candidatos).

XML de Indeed

Si tu ATS ya publica el XML estándar de Indeed, elige "XML de Indeed" en el validador. Las diferencias con el XML Joonow:

  • el id puede venir en referencenumber o requisitionid;
  • la empresa puede venir en company o sourcename;
  • jobtype en inglés (fulltime, parttime, contract, temporary, internship, volunteer, per diem);
  • remotetype con Fully remote o Hybrid remote;
  • email se lee como el correo del responsable del empleo, con el mismo uso que en el XML Joonow (por qué incluirlo);
  • category y experience se ignoran.

XML de LinkedIn

Si tu ATS ya genera el XML de empleos de LinkedIn, elige "XML de LinkedIn". Leemos:

Elemento Uso
partnerJobId Id estable del empleo
title, description, applyUrl Título, descripción y URL de postulación
company, companyId Empresa; el companyId del grupo junto con el nombre separa cada marca
city, state, country Ubicación
listDate, expirationDate Publicación y vigencia
salaries/salary/lowEnd/amount, highEnd/amount, currencyCode, period Salario estructurado (HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY)
jobType FULL_TIME, PART_TIME, CONTRACT, TEMPORARY, INTERNSHIP, VOLUNTEER
experienceLevel INTERNSHIP, ENTRY_LEVEL, ASSOCIATE, MID_SENIOR_LEVEL, DIRECTOR, EXECUTIVE
workplaceTypes On-site, Hybrid o Remote
posterEmail Correo del responsable del empleo, con el mismo uso que email en el XML Joonow (por qué incluirlo)

Cómo leemos el feed

  • Frecuencia: cada 6 horas, de forma predeterminada. La agenda se acuerda en la aprobación, y el panel del socio tiene el botón "Solicitar lectura ahora".
  • GET condicional: enviamos If-None-Match e If-Modified-Since con el ETag y el Last-Modified de la lectura anterior. Una respuesta 304 termina la lectura sin descargar nada; un archivo idéntico al anterior tampoco guarda nada.
  • Compresión: usa la compresión de HTTP (Content-Encoding: gzip, deflate o br). No se acepta un archivo .gz para descargar.
  • Tamaño: hasta 2 GB descomprimido y 15 minutos de descarga.
  • Dirección: URL pública, http o https (prefiere https), hasta 5 redirecciones. Las direcciones de red interna se bloquean.
  • Robot: JoonowFeedBot/1.0 (+https://www.joonow.com/robots.txt). Si tienes firewall, permite este User-Agent.
  • XML: UTF-8, sin <!DOCTYPE> ni entidades externas. Un archivo con DTD se rechaza por seguridad.

Ciclo de vida del empleo

  1. Nuevo: entra en la lectura siguiente y aparece en el sitio en pocos minutos.
  2. Modificado: lo actualizamos cuando cambia el contenido del empleo (título, descripción, ubicación, salario, fechas…).
  3. Cerrado: cuando falta en el archivo en dos lecturas seguidas, cuando pasa la vigencia (expirationdate) o cuando el enlace de postulación está caído en dos revisiones con 24 horas de diferencia.
  4. Antigüedad máxima: un empleo que nunca se renueva (su date no avanza) se cierra a los 60 días; ningún empleo queda publicado más de 180 días. Para mantenerlo, vuelve a publicarlo con la fecha nueva.
  5. Después de cerrado: la página muestra "empleo cerrado" con empleos parecidos y, después de 30 días, responde 410 a los buscadores.

Envía siempre el inventario completo en cada lectura: un archivo parcial cierra los empleos que quedaron fuera.

Válvula de seguridad

Para que un feed roto no tumbe los empleos de tus clientes, la lectura queda en cuarentena cuando:

  • más del 5% de los registros son rechazados; o
  • la cantidad de empleos válidos cae más del 30% respecto a la lectura anterior (con 50 empleos o más).

En cuarentena no se publica ni se cierra nada, y el panel del socio muestra el motivo. Corrige el archivo y solicita una lectura nueva.

Empresa confidencial

El empleo sin company aparece como Confidencial: no tiene página de empresa ni entra en el directorio. Si tu ATS usa su propio nombre en lugar de la empresa en los empleos confidenciales, cuéntalo en la solicitud de integración: tratamos ese nombre como "Confidencial", y tu marca nunca aparece como empresa contratante.

Salario

  • Envíalo solo cuando la empresa lo informa: nunca estimamos salarios.
  • Valor único o rango, con moneda y periodo: MX$ 16.000, MX$ 15.000 a MX$ 18.000 al mes, US$ 25/hora.
  • Separa los miles con punto o no los separes (15.000 o 15000): la coma siempre se lee como separador decimal, así que 15,000 se vuelve 15,00.
  • Sin periodo, lo consideramos mensual. Los empleos con salario entran en el filtro de salario y en el dato estructurado de Google.

Ubicación e idioma

  • Ciudad y estado donde la persona va a trabajar. Un empleo 100% remoto puede venir sin ciudad, con isRemote = Yes.
  • País en código ISO de 2 letras (BR, MX, CO…). Sin país, vale el país predeterminado del feed.
  • Idioma: un feed por idioma (portugués, español o inglés). El idioma se define en la aprobación; para empleos en más de un idioma, envía feeds separados.

Postulación

  • Usa la URL directa del empleo en tu ATS, con https://.
  • Nada de página de inicio, lista de empleos, acortador de enlaces, WhatsApp, Telegram o correo: el empleo queda con aviso o se rechaza.
  • Agregamos utm_source=joonow&utm_medium=referral (si la URL ya tiene utm_source, mantenemos el tuyo). Así mides en tu analítica las postulaciones que vienen de Joonow.
  • ¿Quieres recibir la postulación en tu sistema sin que el candidato salga de Joonow? Activa las postulaciones por webhook.

Calidad y revisión

Cada empleo pasa por reglas de riesgo antes de publicarse: pedir un pago al candidato, postulación por mensajería, correo personal en la descripción, enlace acortado, descripción muy corta, salario fuera de la realidad del cargo, entre otras. Por encima del límite, el empleo queda retenido para revisión del equipo y no aparece hasta ser aprobado. El validador muestra estas señales antes de la integración.

Buenas prácticas para que el empleo se vea bien

  • Título claro, solo con el cargo: "Analista de RR. HH.", y no "EMPLEO 123 – ANALISTA RRHH ¡¡URGENTE!!".
  • Descripción completa (funciones, requisitos, beneficios), en HTML simple con títulos y listas. Más de 200 caracteres.
  • Salario siempre que la empresa lo permita.
  • Correo del responsable en cada empleo: sube en el orden y, si recibe una denuncia, el equipo tiene con quién hablar.
  • Sitio y logo de la empresa en cada empleo (y la descripción, si la tienes): el logo aparece en la búsqueda y ayudan a armar la página de la empresa.
  • Ciudad y estado correctos; el empleo remoto marcado como remoto.
  • Fechas reales de publicación y, cuando la haya, de vigencia.
  • Ids estables: editar el empleo no cambia el referencenumber.
  • Un empleo, un registro: el mismo empleo en varias ciudades se vuelve un registro por ciudad, cada uno con su id.

En Google

Cada empleo de Joonow tiene su propia página con el dato estructurado JobPosting armado con tu feed: baseSalary solo con salario informado, validThrough solo cuando hay vigencia, jobLocationType remoto solo para empleo 100% remoto y el nombre de la empresa como hiringOrganization. Datos completos y correctos en el feed ayudan a que el empleo aparezca en la búsqueda de empleos de Google.

Postulaciones por webhook

Además de enviar empleos, tu ATS puede recibir postulaciones de Joonow. Con el webhook activado, quien se postula a un empleo de tus feeds lo hace en Joonow: ve la empresa, tu sistema como destino y la lista de lo que se envía, confirma, y Joonow entrega la postulación a tu sistema, que crea el registro en nombre de la empresa cliente. Los empleos de quien no lo activó siguen como siempre: el candidato va a la URL de postulación. Rigen los términos de integración para ATS.

Cómo activarlo

En el panel del socio, en Postulaciones:

  1. Acepta los términos de integración (queda registrada la versión, la fecha y quién aceptó).
  2. Indica la dirección HTTPS que recibe los envíos: puerto 443, un nombre de dominio (no una IP) y sin usuario ni contraseña en la URL. No seguimos redirecciones.
  3. Genera la clave secreta. Aparece una sola vez: guárdala en un gestor de secretos.
  4. Envía la postulación de prueba (datos ficticios, "test": true) y responde 2xx.
  5. Activa la recepción. Cambiar la dirección la desactiva hasta una prueba nueva.

El envío

Cada envío es un POST a tu dirección, con Content-Type: application/json; charset=utf-8, cuerpo en UTF-8 y estos encabezados:

Encabezado Ejemplo Para qué sirve
Joonow-Event-Id 0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21 Id único del envío (el mismo id del cuerpo): tu clave de idempotencia.
Joonow-Event-Type application.submitted Tipo del evento (el mismo type del cuerpo).
Joonow-Timestamp 1793628191 Hora de la firma, en segundos Unix.
Joonow-Signature v1=5d4a… HMAC-SHA256 de la clave sobre {timestamp}.{cuerpo}, en hexadecimal.
User-Agent JoonowWebhooks/1.0 Identifica a Joonow (no es prueba: la prueba es la firma).

Tu servidor tiene 10 segundos para responder.

Cuerpo de la postulación (application.submitted, versión 1)

{
  "id": "0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21",
  "type": "application.submitted",
  "version": "1",
  "created_at": "2026-11-02T14:03:11Z",
  "test": false,
  "data": {
    "application": { "id": "0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21", "submitted_at": "2026-11-02T14:03:11Z", "source": "joonow" },
    "job": {
      "external_id": "ACME-4821",
      "joonow_id": "k3j9x2m4pq",
      "title": "Analista de Soporte Junior",
      "company": "Acme Logística",
      "url": "https://www.joonow.com/es/empleo/analista-de-soporte-junior-k3j9x2m4pq"
    },
    "candidate": {
      "name": "María Gómez",
      "email": "maria.gomez@example.com",
      "phone": "+52 55 5555 0100",
      "location": { "city": "Ciudad de México", "region": "CDMX", "country": "MX" },
      "experiences": [
        { "title": "Ejecutiva de atención al cliente", "company": "Tienda Ejemplo", "start": "2023-04", "current": true, "summary": "Atención y posventa." }
      ],
      "education": [
        { "course": "Administración", "institution": "Universidad Ejemplo", "level": "undergraduate", "start": "2019-02", "end": "2022-12", "current": false }
      ],
      "skills": ["Atención al cliente", "Excel"],
      "languages": [{ "name": "Inglés", "level": "intermediate" }],
      "salary_expectation": { "amount": 18000, "currency": "MXN", "period": "month" }
    },
    "answers": [{ "question": "¿Tienes disponibilidad para trabajar los sábados?", "answer": "Sí" }],
    "resume": {
      "file_name": "maria-gomez.pdf",
      "content_type": "application/pdf",
      "size_bytes": 182344,
      "url": "https://www.joonow.com/api/partners/v1/resumes/4bZ9qT…",
      "expires_at": "2026-11-05T14:03:12Z"
    }
  }
}
Campo ¿Va siempre? Qué es
id sí Id del envío (UUID), igual al encabezado Joonow-Event-Id.
type sí application.submitted.
version sí 1. Un campo nuevo puede aparecer sin cambiar la versión; un cambio que rompe pasa a 2, con aviso de 90 días. Ignora lo que no conoces.
created_at sí Cuándo confirmó el candidato (ISO 8601, UTC).
test sí true en la postulación de prueba del panel: responde 2xx, pero no registres a nadie.
data.application.id sí Id de la postulación (el mismo del envío). Con él envías el avance de vuelta.
data.application.source sí joonow.
data.job.external_id sí El id del empleo en tu feed (referencenumber, partnerJobId).
data.job.joonow_id, title, company, url sí El empleo como está en Joonow.
data.candidate.name, email sí Nombre y correo de la cuenta del candidato.
data.candidate.phone si está completo Como lo escribió el candidato.
data.candidate.location si está completo city, region (estado o provincia) y country (ISO 3166-1, 2 letras).
data.candidate.links reservado Lista de { "type", "url" }. Todavía no se envía: llega cuando el perfil tenga enlaces.
data.candidate.experiences si está completo title, company, start y end (AAAA-MM), current, summary.
data.candidate.education si está completo course, institution, level (high_school, technical, undergraduate, postgraduate, masters, doctorate, course), start, end, current.
data.candidate.skills si está completo Lista de textos.
data.candidate.languages si está completo name y level (basic, intermediate, advanced, fluent, native).
data.candidate.salary_expectation si está completo amount, currency (ISO 4217) y period (hour, day, week, month, year).
data.answers sí (puede estar vacía) question y answer: las respuestas a las preguntas del empleo.
data.resume si hay currículum file_name, content_type, size_bytes y el archivo: url con expires_at (enlace) o content_base64 (dentro del envío).

Solo va lo que el candidato completó y vio en la pantalla de confirmación: un campo vacío queda fuera (nada de null ni listas vacías, salvo answers). Los datos sensibles (como discapacidad) no van en esta versión, ni las preguntas sensibles del empleo: la pantalla de confirmación no las muestra.

Currículum

  • Enlace temporal (predeterminado): data.resume.url es un GET firmado que vale 3 días desde cada intento de envío. Descárgalo apenas lo recibas y guárdalo en tu sistema. El enlace no pide inicio de sesión: trátalo como secreto y no lo registres en logs. Vencido, o después de que el candidato elimine su cuenta, responde 404.
  • Dentro del envío: elígelo en el panel; el archivo va en data.resume.content_base64 (PDF o DOCX, hasta 5 MB).

Verificar la firma

  1. Lee el cuerpo crudo, los bytes como llegaron, antes de convertirlo en JSON.
  2. Verifica que Joonow-Timestamp esté a un máximo de 5 minutos de tu hora, hacia adelante o hacia atrás.
  3. Calcula HMAC-SHA256(clave, "{Joonow-Timestamp}.{cuerpo}") en hexadecimal en minúsculas y antepón v1=.
  4. Compara, en tiempo constante, con cada elemento de Joonow-Signature (separados por coma). Basta con que uno coincida: al cambiar la clave van dos, el de la clave nueva y el de la anterior, durante 24 horas.
  5. Sin firma válida o fuera de la ventana: responde 401 y no guardes nada.
import crypto from 'node:crypto';

function firmaValida(cuerpoCrudo, encabezados, clave) {
  const t = encabezados['joonow-timestamp'] ?? '';
  if (!/^\d+$/.test(t) || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const esperada = Buffer.from('v1=' + crypto.createHmac('sha256', clave).update(t + '.').update(cuerpoCrudo).digest('hex'));
  return (encabezados['joonow-signature'] ?? '').split(',').map((s) => Buffer.from(s.trim()))
    .some((s) => s.length === esperada.length && crypto.timingSafeEqual(s, esperada));
}

Respuesta, idempotencia y reintentos

  • Responde 2xx solo después de guardar la postulación. El cuerpo de la respuesta se ignora.
  • El mismo id puede llegar más de una vez (un reintento después de una falla de red, o el candidato reenviando una postulación que no llegó). Si ya tienes ese id, responde 2xx sin registrarla de nuevo.
  • Cualquier otra respuesta, o ninguna en 10 segundos, es una falla. Una redirección (3xx) también.
  • Reintentamos con intervalos crecientes (1 min, 5 min, 30 min, 2 h, 6 h y luego cada 12 h) hasta 72 horas después de la confirmación. Entonces paramos y avisamos por correo al candidato y al contacto de tu ATS.
  • 410 Gone quiere decir "este empleo ya no recibe postulaciones": paramos en el momento y avisamos.
  • El panel muestra cada envío, los intentos, la última respuesta y las fallas.

Avance de vuelta

Para que el candidato siga el avance en Joonow, envía el estado de cada postulación a https://www.joonow.com/api/partners/v1/events, con POST, Content-Type: application/json y los mismos Joonow-Timestamp y Joonow-Signature, calculados con tu clave de la misma forma.

{ "type": "application.in_review", "application_id": "0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21", "occurred_at": "2026-11-03T10:15:00Z" }
  • type: application.received (guardada), application.in_review (en revisión) o application.closed (el proceso terminó para el candidato). El avance solo va hacia adelante: un evento atrasado no lo hace retroceder.
  • Respuestas: 200 con {"ok":true,"applied":true} (o false, cuando nada cambió), 400 para un evento desconocido, 401 para firma, hora o id incorrectos, 413 por encima de 16 KB y 429 en exceso.
  • La postulación de prueba también acepta el avance: úsala para probar la vuelta.
  • El candidato ve el avance solo en su área y recibe un correo cuando la postulación entra en revisión o se cierra.

Solicitudes de corrección y eliminación

Cuando un candidato pide a Joonow corregir o eliminar sus datos, avisamos a cada sistema que recibió una postulación suya (LGPD, art. 18, § 6), por el mismo webhook, con la misma firma y los mismos reintentos:

{
  "id": "0193f611-2c40-7e2b-8a51-0d9e7f3a1c55",
  "type": "candidate.erasure_requested",
  "version": "1",
  "created_at": "2026-11-20T09:00:00Z",
  "test": false,
  "data": {
    "reason": "account_deleted",
    "candidate": { "email": "maria.gomez@example.com" },
    "applications": [{ "id": "0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21", "job_external_id": "ACME-4821", "submitted_at": "2026-11-02T14:03:11Z" }],
    "respond_by": "2026-12-05T09:00:00Z"
  }
}
  • candidate.erasure_requested: elimina (o pide a la empresa cliente que elimine) lo que vino de Joonow. reason es account_deleted (el candidato eliminó la cuenta) o candidate_request (lo pidió en el centro de privacidad).
  • candidate.correction_requested: data.candidate trae los datos actuales, en el mismo formato de la postulación, para reemplazar los que recibiste.
  • Transmítelo a la empresa cliente y confirma en hasta 15 días (respond_by) con un evento de vuelta: { "type": "candidate.erasure_completed", "request_id": "<id de la solicitud>" } (o candidate.correction_completed).

Seguridad

  • Solo HTTPS, con TLS 1.2 o más reciente. Rechaza envíos sin firma válida o fuera de la ventana de 5 minutos.
  • Guarda la clave en un gestor de secretos y cámbiala en el panel si sospechas de una filtración.
  • No registres el cuerpo de las postulaciones ni los enlaces de currículum en logs abiertos.
  • Los envíos salen de direcciones IP que pueden cambiar: confía en la firma, no en la IP.

Cómo solicitar la integración

  1. Arma el feed siguiendo esta especificación.
  2. Pega la URL en el validador y corrige lo que señale.
  3. Con el feed validado, completa la solicitud ahí mismo. El equipo la revisa en hasta 2 días hábiles.
  4. Una vez aprobada, recibes por correo el acceso al panel del socio, con lecturas, empleos publicados, rechazos y clics por mes.

¿Dudas técnicas? Habla con nosotros por Contáctanos.