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 (
ETageLast-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
referencenumberourequisitionid; - a empresa pode vir em
companyousourcename; jobtypeem inglês (fulltime,parttime,contract,temporary,internship,volunteer,per diem);remotetypecomFully remoteouHybrid remote;emailé lido como o e-mail do responsável pela vaga, com o mesmo uso do XML Joonow (por que incluir);categoryeexperiencesã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-MatcheIf-Modified-Sincecom oETage oLast-Modifiedda leitura anterior. Resposta304encerra 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,deflateoubr). Arquivo.gzpara baixar não é aceito. - Tamanho: até 2 GB descompactado e 15 minutos de download.
- Endereço: URL pública,
httpouhttps(prefirahttps), 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
- Nova: entra na leitura seguinte e aparece no site em poucos minutos.
- Alterada: atualizamos quando o conteúdo da vaga muda (título, descrição, local, salário, datas…).
- 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. - Idade máxima: uma vaga que nunca é renovada (a
datenã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. - 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.800ou3800): a vírgula é sempre lida como separador decimal, então3,800vira 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á tiverutm_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:
- Aceite os termos de integração (fica registrado a versão, a data e quem aceitou).
- 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.
- Gere a chave secreta. Ela aparece uma vez: guarde num cofre de segredos.
- Mande a candidatura de teste (dados fictícios,
"test": true) e responda 2xx. - 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é umGETassinado 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
- Leia o corpo cru, os bytes como chegaram, antes de converter em JSON.
- Confira que
Joonow-Timestampestá a no máximo 5 minutos da sua hora, para mais ou para menos. - Calcule
HMAC-SHA256(chave, "{Joonow-Timestamp}.{corpo}")em hexadecimal minúsculo e ponhav1=na frente. - 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. - 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
idpode 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 esseid, 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) ouapplication.closed(o processo terminou para o candidato). O andamento só anda para frente: um evento atrasado não volta a situação.- Respostas:
200com{"ok":true,"applied":true}(oufalse, quando nada mudou),400para evento desconhecido,401para assinatura, hora ou id errados,413acima de 16 KB e429em 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) oucandidate_request(pediu pela central de privacidade).candidate.correction_requested:data.candidatetraz 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>" }(oucandidate.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
- Monte o feed seguindo esta especificação.
- Cole a URL no validador e corrija o que ele apontar.
- Com o feed validado, preencha o pedido ali mesmo. A equipe confere em até 2 dias úteis.
- 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.