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 (
ETagyLast-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
referencenumberorequisitionid; - la empresa puede venir en
companyosourcename; jobtypeen inglés (fulltime,parttime,contract,temporary,internship,volunteer,per diem);remotetypeconFully remoteoHybrid remote;emailse lee como el correo del responsable del empleo, con el mismo uso que en el XML Joonow (por qué incluirlo);categoryyexperiencese 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-MatcheIf-Modified-Sincecon elETagy elLast-Modifiedde la lectura anterior. Una respuesta304termina 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,deflateobr). No se acepta un archivo.gzpara descargar. - Tamaño: hasta 2 GB descomprimido y 15 minutos de descarga.
- Dirección: URL pública,
httpohttps(prefierehttps), 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
- Nuevo: entra en la lectura siguiente y aparece en el sitio en pocos minutos.
- Modificado: lo actualizamos cuando cambia el contenido del empleo (título, descripción, ubicación, salario, fechas…).
- 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. - Antigüedad máxima: un empleo que nunca se renueva (su
dateno 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. - 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.000o15000): la coma siempre se lee como separador decimal, así que15,000se 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 tieneutm_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:
- Acepta los términos de integración (queda registrada la versión, la fecha y quién aceptó).
- 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.
- Genera la clave secreta. Aparece una sola vez: guárdala en un gestor de secretos.
- Envía la postulación de prueba (datos ficticios,
"test": true) y responde 2xx. - 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.urles unGETfirmado 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
- Lee el cuerpo crudo, los bytes como llegaron, antes de convertirlo en JSON.
- Verifica que
Joonow-Timestampesté a un máximo de 5 minutos de tu hora, hacia adelante o hacia atrás. - Calcula
HMAC-SHA256(clave, "{Joonow-Timestamp}.{cuerpo}")en hexadecimal en minúsculas y antepónv1=. - 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. - 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
idpuede 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 eseid, 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) oapplication.closed(el proceso terminó para el candidato). El avance solo va hacia adelante: un evento atrasado no lo hace retroceder.- Respuestas:
200con{"ok":true,"applied":true}(ofalse, cuando nada cambió),400para un evento desconocido,401para firma, hora o id incorrectos,413por encima de 16 KB y429en 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.reasonesaccount_deleted(el candidato eliminó la cuenta) ocandidate_request(lo pidió en el centro de privacidad).candidate.correction_requested:data.candidatetrae 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>" }(ocandidate.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
- Arma el feed siguiendo esta especificación.
- Pega la URL en el validador y corrige lo que señale.
- Con el feed validado, completa la solicitud ahí mismo. El equipo la revisa en hasta 2 días hábiles.
- 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.