Especificação do feed de vagas para ATS

Formatos aceitos (XML Joonow, Indeed e LinkedIn), cada campo com exemplo, regras de leitura e encerramento e boas práticas para o feed de vagas do seu ATS.

19 min de leitura Versão em Markdown

Neste artigo
  1. Resumo
  2. Formatos aceitos
  3. XML Joonow
  4. XML do Indeed
  5. XML do LinkedIn
  6. Como lemos o feed
  7. Ciclo de vida da vaga
  8. Válvula de segurança
  9. Empresa confidencial
  10. Salário
  11. Local e idioma
  12. Candidatura
  13. Qualidade e revisão
  14. Boas práticas para a vaga aparecer bem
  15. Candidaturas por webhook
  16. Como pedir a integração

Esta é a especificação pública do feed de vagas da Joonow para sistemas de recrutamento (ATS) e sites de carreira. Com ela você monta o feed, confere no validador e pede a integração sem falar com ninguém. O programa é gratuito: veja o que o seu ATS ganha.

Resumo

  • Um arquivo XML numa URL pública, de preferência com https://, com todas as vagas abertas dos seus clientes.
  • Cada vaga precisa de id estável, título, descrição, URL de candidatura e empresa.
  • Recomendado: o e-mail de quem responde pela vaga (email). Não aparece no site e faz a vaga subir na ordenação (por que incluir).
  • Recomendado: o site e a logo da empresa (companyurl, companylogo); a descrição (companydescription) é opcional. A logo aparece nas vagas, na busca e no diretório, e os três ajudam a montar a página da empresa (por que incluir).
  • Lemos o feed a cada 6 horas (a agenda é combinada na aprovação), com GET condicional (ETag e Last-Modified).
  • Vaga que sai do arquivo é encerrada na segunda leitura seguida sem ela.
  • A candidatura acontece no seu site: o botão leva à URL da vaga com utm_source=joonow. Ou, com as candidaturas por webhook ligadas, o candidato se candidata na Joonow e a candidatura chega assinada ao seu sistema.
  • Antes de pedir, valide no validador: ele mostra os erros de cada vaga e não grava nada.

Formatos aceitos

Formato Quando usar Elemento de cada vaga
XML Joonow (recomendado) Você vai criar o feed agora ou já gera o XML do Indeed. <job>
XML do Indeed O seu ATS já publica o XML padrão do Indeed (requisitionid, sourcename, remotetype). <job>
XML do LinkedIn O seu ATS já gera o XML de vagas do LinkedIn (partnerJobId, applyUrl). <job>

Os três usam a raiz <source> com uma lista de <job>. Os nomes dos elementos não diferenciam maiúsculas e o texto pode vir dentro de CDATA.

XML Joonow

É o XML que muitos ATS já geram para o Indeed, com alguns campos a mais (validade, modelo híbrido e nível). Baixe o XML de exemplo e o esquema XSD.

<?xml version="1.0" encoding="UTF-8"?>
<source>
  <publisher>Nome do seu ATS</publisher>
  <publisherurl>https://www.seu-ats.com</publisherurl>
  <lastBuildDate>2026-10-30T09:00:00Z</lastBuildDate>
  <job>
    <referencenumber>ACME-4821</referencenumber>
    <title><![CDATA[Analista de Suporte Júnior]]></title>
    <company><![CDATA[Acme Logística]]></company>
    <email>recrutamento@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 com armazéns em Pernambuco e na Bahia.]]></companydescription>
    <url><![CDATA[https://acme.seu-ats.com/vagas/4821]]></url>
    <date>2026-10-28T13:00:00Z</date>
    <expirationdate>2026-12-15T23:59:59Z</expirationdate>
    <city><![CDATA[Recife]]></city>
    <state>PE</state>
    <country>BR</country>
    <description><![CDATA[<p>Atendimento a clientes...</p><h3>Requisitos</h3><ul><li>...</li></ul>]]></description>
    <salary><![CDATA[R$ 2.800,00 a R$ 3.200,00 por mês]]></salary>
    <jobtype>fulltime</jobtype>
    <category>Profissional</category>
    <remotetype>Hybrid remote</remotetype>
  </job>
</source>

Campos da vaga

Elemento Obrigatório Tipo Exemplo Como a Joonow usa
referencenumber Sim Texto, até 200 caracteres ACME-4821 Identifica a vaga entre uma leitura e outra. Precisa ser único e estável: se mudar, a vaga vira outra. Sem ele, usamos a URL de candidatura.
title Sim Texto, até 200 caracteres Analista de Suporte Júnior Título da vaga, na busca e na página. Título todo em CAIXA ALTA vira caixa normal (siglas ficam).
description Sim HTML simples, até 50 KB <p>…</p><ul><li>…</li></ul> Descrição da vaga. Ficam só p, br, ul, ol, li, strong, b, em, i, u, h2, h3, h4, a, blockquote e hr; o resto (script, estilo, imagem) sai.
url Sim URL https:// https://acme.seu-ats.com/vagas/4821 Destino do botão "Candidatar-se", com utm_source=joonow. Precisa abrir a própria vaga.
company Sim Texto Acme Logística Nome da empresa: cria a página da empresa com todas as vagas dela. Vazio vira "Confidencial".
city Recomendado Texto Recife Cidade: filtros, busca por local e páginas de cidade.
state Recomendado Sigla ou nome PE Estado. No Brasil, a sigla (UF) ou o nome por extenso.
country Recomendado Código ISO de 2 letras ou nome BR País. Vazio usa o país padrão do feed, definido na aprovação.
date Recomendado Data (ISO 8601 ou RFC 1123) 2026-10-28T13:00:00Z Data de publicação: ordenação, "publicada há X dias" e idade da vaga. Sem ela, usamos a data da leitura; data no futuro vira hoje.
email Recomendado E-mail, até 254 caracteres recrutamento@acme.example E-mail profissional de quem responde pela vaga. Não aparece no site. A vaga ganha pontos na ordenação e, se receber uma denúncia, a equipe da Joonow tem com quem falar (por que incluir). Inválido é ignorado, sem recusar a vaga.
companyurl Recomendado URL absoluta https://www.acme.example/ Site da própria empresa (não o do ATS, de rede social nem de página de vagas). Vira o link da página da empresa e ajuda a achar a logo e as redes (por que incluir).
companylogo Recomendado URL absoluta de imagem https://www.acme.example/img/logo.png Logo da empresa em PNG, JPEG ou WebP, com 128 px ou mais (quadrada é melhor). A Joonow baixa uma cópia e serve do próprio site.
companydescription Opcional Texto ou HTML simples, até 4.000 caracteres Operador logístico com armazéns no Nordeste. O que a empresa faz, para a página dela. Repita o mesmo texto em todas as vagas da empresa.
expirationdate Opcional Data (ISO 8601) 2026-12-15T23:59:59Z Validade: depois dela, a vaga é encerrada. Vaga que já chega vencida é recusada.
salary Opcional Texto com valor ou faixa R$ 2.800,00 a R$ 3.200,00 por mês Salário, só quando a empresa informa. Reconhecemos moeda (R$, US$, €), faixa e período (hora, dia, semana, mês, ano).
jobtype Opcional Código fulltime Tipo de contrato (valores abaixo).
category Opcional Código Profissional Nível da vaga (valores abaixo). Outros valores são ignorados.
isRemote Opcional Yes ou No Yes Yes = 100% remota; No = presencial.
remotetype Opcional Texto Hybrid remote Fully remote = 100% remota; Hybrid remote = híbrida. Vale quando isRemote não vem.

Os elementos publisher, publisherurl e lastBuildDate da raiz são opcionais: ajudam a identificar o arquivo, mas não mudam as vagas.

Por que incluir o e-mail do responsável

O email (no XML do LinkedIn, posterEmail) é o e-mail profissional de quem responde pela vaga: a pessoa recrutadora ou a caixa da equipe de RH da empresa. Não é obrigatório, mas vale a pena:

  • A vaga sobe na busca. O e-mail soma pontos na nota de qualidade do anúncio, como o salário e a descrição completa, e essa nota desempata a ordem entre as vagas que combinam com a busca: com o e-mail, a vaga passa à frente das equivalentes sem ele. Aparecer antes costuma render mais visitas e mais candidaturas.
  • Tem com quem falar antes de a vaga sair do ar. Quando a vaga recebe uma denúncia de algo que dá para corrigir (dado errado, link trocado), a equipe da Joonow escreve para esse e-mail antes de decidir se a tira do ar.
  • O e-mail fica só com a equipe da Joonow. Ele nunca aparece na vaga, nas buscas, na API nem para candidatos, não é vendido nem usado para propaganda e é apagado 30 dias depois de a vaga sair do ar (política de privacidade).

Mande um endereço completo por vaga (recrutamento@empresa.com.br). Caixa "não responda" não serve. Um e-mail inválido é ignorado sem recusar a vaga (ela entra sem o ponto), e o validador mostra quantas vagas trazem o e-mail.

Por que incluir site, logo e descrição da empresa

O companyurl, o companylogo e o companydescription montam a página de cada empresa cliente na Joonow, com o mesmo valor em todas as vagas dela:

  • A logo aparece em todo lugar: no cartão da vaga, na busca, na vaga aberta e no diretório de empresas, e vai no JSON-LD da vaga para o Google.
  • Ajudam a montar a página da empresa: o site vira o link da página depois de conferido, as redes sociais vêm dele, e a descrição entra, junto com o próprio site da empresa, no texto curto que escrevemos sobre ela (nunca publicamos a descrição como veio).
  • Nada disso muda a vaga: site, logo e descrição são da empresa. Mudar a logo não regrava as vagas, e um valor inválido é ignorado sem recusar a vaga.

A logo é baixada uma vez e servida pela Joonow (nunca carregamos imagem do seu servidor na página). Se a empresa reivindicar a página, o que ela escrever vale no lugar do que veio do feed. O validador mostra quantas vagas trazem a logo e o site.

Valores aceitos

jobtype (tipo de contrato): fulltime (efetivo, tempo integral), parttime (meio período), contract (prestador de serviços, PJ, freelancer), temporary (temporário), internship (estágio) e volunteer (voluntário). Também aceitamos os rótulos em português: Efetivo – CLT, Estatutário, Estágio, Prestador de Serviços - PJ, Freelancer, Autônomo, Cooperado, Temporário, Tempo Determinado e Intermitente.

category (nível): os códigos Apprentice (aprendiz), Intern (estágio), Entry (início de carreira), Operational (operacional), Professional (profissional), Management (coordenação e gerência) e Executive (diretoria), ou os rótulos em português Aprendiz, Estágio, Trainee, Operacional, Profissional, Supervisão/Coordenação e Gerencial e Executivos.

Modelo de trabalho: isRemote com Yes ou No, ou remotetype com Fully remote, Hybrid remote ou On-site. "COVID-19" (remoto só temporariamente) fica sem modelo.

O que não usar

O elemento priority existe no nosso leitor por compatibilidade, mas não faz parte do programa: vaga patrocinada não é combinada pelo feed. phone e campos internos do ATS são ignorados. Além do e-mail do responsável pela vaga, não mande dados pessoais no feed (nunca dados de candidatos).

XML do Indeed

Se o seu ATS já publica o XML padrão do Indeed, escolha "XML do Indeed" no validador. As diferenças para o XML Joonow:

  • o id pode vir em referencenumber ou requisitionid;
  • a empresa pode vir em company ou sourcename;
  • jobtype em inglês (fulltime, parttime, contract, temporary, internship, volunteer, per diem);
  • remotetype com Fully remote ou Hybrid remote;
  • email é lido como o e-mail do responsável pela vaga, com o mesmo uso do XML Joonow (por que incluir);
  • category e experience são ignorados.

XML do LinkedIn

Se o seu ATS já gera o XML de vagas do LinkedIn, escolha "XML do LinkedIn". Lemos:

Elemento Uso
partnerJobId Id estável da vaga
title, description, applyUrl Título, descrição e URL de candidatura
company, companyId Empresa; o companyId do grupo junto com o nome separa cada marca
city, state, country Local
listDate, expirationDate Publicação e validade
salaries/salary/lowEnd/amount, highEnd/amount, currencyCode, period Salário estruturado (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 ou Remote
posterEmail E-mail do responsável pela vaga, com o mesmo uso do email do XML Joonow (por que incluir)

Como lemos o feed

  • Frequência: a cada 6 horas, no padrão. A agenda é combinada na aprovação, e o painel do parceiro tem o botão "Pedir leitura agora".
  • GET condicional: mandamos If-None-Match e If-Modified-Since com o ETag e o Last-Modified da leitura anterior. Resposta 304 encerra a leitura sem baixar nada; arquivo idêntico ao anterior também não grava nada.
  • Compressão: use a compressão do HTTP (Content-Encoding: gzip, deflate ou br). Arquivo .gz para baixar não é aceito.
  • Tamanho: até 2 GB descompactado e 15 minutos de download.
  • Endereço: URL pública, http ou https (prefira https), até 5 redirecionamentos. Endereços de rede interna são bloqueados.
  • Robô: JoonowFeedBot/1.0 (+https://www.joonow.com/robots.txt). Se houver firewall, libere esse User-Agent.
  • XML: UTF-8, sem <!DOCTYPE> nem entidades externas. Um arquivo com DTD é recusado por segurança.

Ciclo de vida da vaga

  1. Nova: entra na leitura seguinte e aparece no site em poucos minutos.
  2. Alterada: atualizamos quando o conteúdo da vaga muda (título, descrição, local, salário, datas…).
  3. Encerrada: quando some do arquivo em duas leituras seguidas, quando a validade (expirationdate) passa ou quando o link de candidatura fica fora do ar em duas conferências com 24 horas de intervalo.
  4. Idade máxima: uma vaga que nunca é renovada (a date não avança) é encerrada depois de 60 dias; nenhuma vaga fica mais de 180 dias no ar. Para manter a vaga, republique com a data nova.
  5. Depois de encerrada: a página mostra "vaga encerrada" com vagas parecidas e, depois de 30 dias, responde 410 para os buscadores.

Mande sempre o inventário completo a cada leitura: um arquivo parcial encerra as vagas que ficaram de fora.

Válvula de segurança

Para um feed quebrado não derrubar as vagas dos seus clientes, a leitura vai para quarentena quando:

  • mais de 5% dos registros são recusados; ou
  • o número de vagas válidas cai mais de 30% em relação à leitura anterior (com 50 vagas ou mais).

Em quarentena nada é publicado nem encerrado, e o painel do parceiro mostra o motivo. Corrija o arquivo e peça uma leitura nova.

Empresa confidencial

Vaga sem company aparece como Confidencial: não tem página de empresa nem entra no diretório. Se o seu ATS usa o próprio nome no lugar da empresa nas vagas confidenciais, conte no pedido de integração: tratamos esse nome como "Confidencial", e a sua marca nunca aparece como empresa contratante.

Salário

  • Mande só quando a empresa informa: nunca estimamos salário.
  • Valor único ou faixa, com moeda e período: R$ 3.000,00, R$ 2.800,00 a R$ 3.200,00 por mês, US$ 25/hora.
  • Separe o milhar com ponto ou não separe (3.800 ou 3800): a vírgula é sempre lida como separador decimal, então 3,800 vira 3,80.
  • Sem período, consideramos mensal. Vagas com salário entram no filtro de salário e no dado estruturado do Google.

Local e idioma

  • Cidade e estado de onde a pessoa vai trabalhar. Vaga 100% remota pode vir sem cidade, com isRemote = Yes.
  • País em código ISO de 2 letras (BR, PT, MX…). Sem país, vale o país padrão do feed.
  • Idioma: um feed por idioma (português, espanhol ou inglês). O idioma é definido na aprovação; para vagas em mais de um idioma, mande feeds separados.

Candidatura

  • Use a URL direta da vaga no seu ATS, com https://.
  • Nada de página inicial, lista de vagas, encurtador de link, WhatsApp, Telegram ou e-mail: a vaga fica com aviso ou é recusada.
  • Acrescentamos utm_source=joonow&utm_medium=referral (se a URL já tiver utm_source, mantemos a sua). Assim você mede no seu analytics as candidaturas vindas da Joonow.
  • Quer receber a candidatura no seu sistema, sem o candidato sair da Joonow? Ligue as candidaturas por webhook.

Qualidade e revisão

Cada vaga passa por regras de risco antes de ir ao ar: pedido de pagamento ao candidato, candidatura por mensageiro, e-mail pessoal na descrição, link encurtado, descrição muito curta, salário fora da realidade do cargo, entre outras. Acima do limite, a vaga fica retida para revisão da equipe e não aparece até ser aprovada. O validador mostra esses sinais antes da integração.

Boas práticas para a vaga aparecer bem

  • Título claro, só com o cargo: "Analista de RH", e não "VAGA 123 – ANALISTA RH URGENTE!!!".
  • Descrição completa (atribuições, requisitos, benefícios), em HTML simples com títulos e listas. Mais de 200 caracteres.
  • Salário sempre que a empresa permitir.
  • E-mail do responsável em cada vaga: ela sobe na ordenação e, se receber uma denúncia, a equipe tem com quem falar.
  • Site e logo da empresa em cada vaga (e a descrição, se tiver): a logo aparece na busca e eles ajudam a montar a página da empresa.
  • Cidade e estado corretos; vaga remota marcada como remota.
  • Datas reais de publicação e, quando houver, de validade.
  • Ids estáveis: editar a vaga não muda o referencenumber.
  • Uma vaga, um registro: a mesma vaga em várias cidades vira um registro por cidade, cada um com o seu id.

No Google

Cada vaga da Joonow tem página própria com o dado estruturado JobPosting montado com o seu feed: baseSalary só com salário informado, validThrough só quando há validade, jobLocationType remoto só para vaga 100% remota e o nome da empresa como hiringOrganization. Dados completos e corretos no feed ajudam a vaga a aparecer na busca de vagas do Google.

Candidaturas por webhook

Além de mandar vagas, o seu ATS pode receber candidaturas da Joonow. Com o webhook ligado, quem se candidata a uma vaga dos seus feeds faz isso na própria Joonow: vê a empresa, o seu sistema como destino e a lista do que vai, confirma, e a Joonow entrega a candidatura ao seu sistema, que faz o cadastro em nome da empresa cliente. As vagas de quem não ligou continuam como sempre: o candidato segue para a URL de candidatura. Valem os termos de integração para ATS.

Como ligar

No painel do parceiro, em Candidaturas:

  1. Aceite os termos de integração (fica registrado a versão, a data e quem aceitou).
  2. Informe o endereço HTTPS que recebe os envios: porta 443, um nome de domínio (não um IP) e sem usuário e senha na URL. Não seguimos redirecionamentos.
  3. Gere a chave secreta. Ela aparece uma vez: guarde num cofre de segredos.
  4. Mande a candidatura de teste (dados fictícios, "test": true) e responda 2xx.
  5. Ligue o recebimento. Trocar o endereço desliga até um teste novo.

O envio

Cada envio é um POST no seu endereço, com Content-Type: application/json; charset=utf-8, corpo em UTF-8 e estes cabeçalhos:

Cabeçalho Exemplo Para que serve
Joonow-Event-Id 0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21 Id único do envio (o mesmo id do corpo): a sua chave de idempotência.
Joonow-Event-Type application.submitted Tipo do evento (o mesmo type do corpo).
Joonow-Timestamp 1793628191 Hora da assinatura, em segundos Unix.
Joonow-Signature v1=5d4a… HMAC-SHA256 da chave sobre {timestamp}.{corpo}, em hexadecimal.
User-Agent JoonowWebhooks/1.0 Identifica a Joonow (não serve como prova: a prova é a assinatura).

O seu servidor tem 10 segundos para responder.

Corpo da candidatura (application.submitted, versão 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 Suporte Júnior",
      "company": "Acme Logística",
      "url": "https://www.joonow.com/pt-br/vaga/analista-de-suporte-junior-k3j9x2m4pq"
    },
    "candidate": {
      "name": "Maria Souza",
      "email": "maria.souza@example.com",
      "phone": "+55 81 99999-0000",
      "location": { "city": "Recife", "region": "PE", "country": "BR" },
      "experiences": [
        { "title": "Atendente", "company": "Loja Exemplo", "start": "2023-04", "current": true, "summary": "Atendimento e pós-venda." }
      ],
      "education": [
        { "course": "Administração", "institution": "Faculdade Exemplo", "level": "undergraduate", "start": "2019-02", "end": "2022-12", "current": false }
      ],
      "skills": ["Atendimento ao cliente", "Excel"],
      "languages": [{ "name": "Inglês", "level": "intermediate" }],
      "salary_expectation": { "amount": 3000, "currency": "BRL", "period": "month" }
    },
    "answers": [{ "question": "Tem disponibilidade para trabalhar aos sábados?", "answer": "Sim" }],
    "resume": {
      "file_name": "maria-souza.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 Vai sempre? O que é
id sim Id do envio (UUID), igual ao cabeçalho Joonow-Event-Id.
type sim application.submitted.
version sim 1. Campo novo pode aparecer sem mudar a versão; mudança que quebra vira 2, com aviso de 90 dias. Ignore o que não conhece.
created_at sim Quando o candidato confirmou (ISO 8601, UTC).
test sim true na candidatura de teste do painel: responda 2xx, mas não cadastre ninguém.
data.application.id sim Id da candidatura (o mesmo do envio). É com ele que você manda o andamento de volta.
data.application.source sim joonow.
data.job.external_id sim O id da vaga no seu feed (referencenumber, partnerJobId).
data.job.joonow_id, title, company, url sim A vaga como está na Joonow.
data.candidate.name, email sim Nome e e-mail da conta do candidato.
data.candidate.phone se preenchido Como o candidato digitou.
data.candidate.location se preenchido city, region (UF ou estado) e country (ISO 3166-1, 2 letras).
data.candidate.links reservado Lista de { "type", "url" }. Ainda não vai: entra quando o perfil tiver links.
data.candidate.experiences se preenchido title, company, start e end (AAAA-MM), current, summary.
data.candidate.education se preenchido course, institution, level (high_school, technical, undergraduate, postgraduate, masters, doctorate, course), start, end, current.
data.candidate.skills se preenchido Lista de textos.
data.candidate.languages se preenchido name e level (basic, intermediate, advanced, fluent, native).
data.candidate.salary_expectation se preenchido amount, currency (ISO 4217) e period (hour, day, week, month, year).
data.answers sim (pode ser vazia) question e answer: as respostas às perguntas da vaga.
data.resume se houver currículo file_name, content_type, size_bytes e o arquivo: url com expires_at (link) ou content_base64 (no envio).

Só vai o que o candidato preencheu e viu na tela de confirmação: campo vazio fica de fora (nada de null nem lista vazia, exceto answers). Dado sensível (como deficiência) não vai nesta versão, nem as perguntas sensíveis da vaga: a tela de confirmação não as mostra.

Currículo

  • Link temporário (padrão): data.resume.url é um GET assinado que vale 3 dias a partir de cada tentativa de envio. Baixe logo ao receber e guarde no seu sistema. O link não pede login: trate-o como segredo e não o registre em logs. Vencido, ou depois que o candidato exclui a conta, responde 404.
  • No envio: escolha no painel; o arquivo vai em data.resume.content_base64 (PDF ou DOCX, até 5 MB).

Conferir a assinatura

  1. Leia o corpo cru, os bytes como chegaram, antes de converter em JSON.
  2. Confira que Joonow-Timestamp está a no máximo 5 minutos da sua hora, para mais ou para menos.
  3. Calcule HMAC-SHA256(chave, "{Joonow-Timestamp}.{corpo}") em hexadecimal minúsculo e ponha v1= na frente.
  4. Compare, em tempo constante, com cada item de Joonow-Signature (separados por vírgula). Basta um conferir: na troca da chave vão dois, o da chave nova e o da anterior, por 24 horas.
  5. Sem assinatura válida ou fora da janela: responda 401 e não grave nada.
import crypto from 'node:crypto';

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

Resposta, idempotência e novas tentativas

  • Responda 2xx só depois de gravar a candidatura. O corpo da resposta é ignorado.
  • O mesmo id pode chegar mais de uma vez (nova tentativa depois de uma falha de rede ou o candidato reenviando uma candidatura que não chegou). Se você já tem esse id, responda 2xx sem cadastrar de novo.
  • Qualquer outra resposta, ou nenhuma em 10 segundos, é falha. Redirecionamento (3xx) também é falha.
  • Tentamos de novo com intervalos crescentes (1 min, 5 min, 30 min, 2 h, 6 h e depois a cada 12 h) até 72 horas depois da confirmação. Então paramos e avisamos por e-mail o candidato e o contato do seu ATS.
  • 410 Gone quer dizer "esta vaga não recebe mais candidaturas": paramos na hora e avisamos.
  • O painel mostra cada envio, as tentativas, a última resposta e as falhas.

Andamento de volta

Para o candidato acompanhar na Joonow, mande o andamento de cada candidatura para https://www.joonow.com/api/partners/v1/events, com POST, Content-Type: application/json e os mesmos Joonow-Timestamp e Joonow-Signature, calculados com a sua chave do mesmo jeito.

{ "type": "application.in_review", "application_id": "0193f5c2-7b1e-7c4a-9d2e-5a1f0c3b8e21", "occurred_at": "2026-11-03T10:15:00Z" }
  • type: application.received (gravou), application.in_review (em análise) ou application.closed (o processo terminou para o candidato). O andamento só anda para frente: um evento atrasado não volta a situação.
  • Respostas: 200 com {"ok":true,"applied":true} (ou false, quando nada mudou), 400 para evento desconhecido, 401 para assinatura, hora ou id errados, 413 acima de 16 KB e 429 em excesso.
  • A candidatura de teste também aceita o andamento: use para testar a volta.
  • O candidato vê o andamento só na área dele e recebe um e-mail quando a candidatura entra em análise ou é encerrada.

Pedidos de correção e exclusão

Quando o candidato pede à Joonow para corrigir ou apagar os dados dele, avisamos cada sistema que recebeu uma candidatura dele (LGPD, art. 18, § 6º), pelo mesmo webhook, com a mesma assinatura e as mesmas novas tentativas:

{
  "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.souza@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: apague (ou peça à empresa cliente que apague) o que veio da Joonow. reason é account_deleted (o candidato excluiu a conta) ou candidate_request (pediu pela central de privacidade).
  • candidate.correction_requested: data.candidate traz os dados de agora, no mesmo formato da candidatura, para trocar os que você recebeu.
  • Repasse à empresa cliente e confirme em até 15 dias (respond_by) com um evento de volta: { "type": "candidate.erasure_completed", "request_id": "<id do pedido>" } (ou candidate.correction_completed).

Segurança

  • Só HTTPS, com TLS 1.2 ou mais novo. Recuse envios sem assinatura válida ou fora da janela de 5 minutos.
  • Guarde a chave num cofre de segredos e troque no painel se suspeitar de vazamento.
  • Não registre o corpo das candidaturas nem os links de currículo em logs abertos.
  • Os envios saem de endereços de IP que podem mudar: confie na assinatura, não no IP.

Como pedir a integração

  1. Monte o feed seguindo esta especificação.
  2. Cole a URL no validador e corrija o que ele apontar.
  3. Com o feed validado, preencha o pedido ali mesmo. A equipe confere em até 2 dias úteis.
  4. Aprovado, você recebe por e-mail o acesso ao painel do parceiro, com leituras, vagas no ar, recusas e cliques por mês.

Dúvidas técnicas? Fale com a gente pelo Fale conosco.