Avisos (webhook)
O Motor faz POST no endereço do webhook da integração da empresa quando a admissão chega a:
| aviso | quando |
|---|---|
admissao.processada | o eSocial aceitou; leva o nr_recibo |
admissao.recusada | o eSocial recusou; leva as ocorrencias. O escritório também é avisado |
admissao.descartada | o escritório descartou o evento |
admissao.aguardando_escritorio | com o envio automático ligado, quando ele para no meio e o evento cai na fila do escritório; e quando a chave que enviou a admissão é revogada antes da transmissão |
O "recebido" é a própria resposta do POST. Com o envio automático desligado, esperar o escritório é o normal e não gera aviso.
Por que o envio automático para
O motivo do aviso (e da consulta) diz por que a admissão passou a esperar uma pessoa do escritório. Os casos, em resumo — o texto exato pode mudar, então não decida pelo texto:
- o evento não pôde ser montado com os dados do cadastro;
- o envio automático foi desligado, ou a chave que enviou a admissão foi revogada;
- a admissão foi alterada no Motor depois de recebida (dados ou montagem do evento) — dali em diante quem aprova é uma pessoa, nunca o envio automático;
- o envio teve um erro técnico, o certificado não pôde ser usado, ou o eSocial precisa de outro evento da empresa antes deste — o envio automático nunca tenta de novo;
- o escritório que atende a empresa está com o serviço suspenso;
- uma falha do Motor impediu o envio automático de terminar.
Em todos eles a admissão segue na fila do escritório, e o resultado final (processada, recusada ou descartada) chega por aviso como sempre.
O corpo
Cabeçalhos: X-Motor-Aviso-Id, X-Motor-Evento e X-Motor-Assinatura.
{
"id": "c0e1d2f3-0000-4000-8000-000000000001",
"evento": "admissao.processada",
"ocorrido_em": "2026-10-28T13:07:40-03:00",
"ambiente": "PRODUCAO",
"admissao": {
"id": "9a3f0c55-7d7e-4a52-b8c1-5d9f3c2e1a10",
"estado": "PROCESSADA",
"matricula": "MOT-000042",
"nr_recibo": "1.1.0000000000123456789",
"ocorrencias": [],
"motivo": null
}
}
O aviso nunca leva o XML nem dados pessoais (CPF, salário, endereço): se o endereço do webhook estiver errado ou for tomado, nada disso vaza. Busque os XML pela consulta.
Regras de entrega
- Pelo menos uma vez. O mesmo aviso pode chegar mais de uma vez: descarte o que tiver
idjá processado. - Sem ordem garantida. Vale o mais recente por
ocorrido_em; ignore aviso mais antigo que o último que você aplicou àquela admissão. - Responda
2xxem até 10 segundos e processe depois. Qualquer outra resposta, ou nenhuma, gera nova tentativa, com intervalo crescente, por 72 horas. Depois disso o Motor para e avisa o escritório; o resultado continua na consulta. - Redirecionamento não é seguido: um
3xxconta como falha de entrega. Cadastre o endereço final. - Sem webhook cadastrado, os avisos não são enviados nem contam tentativa. Se o escritório cadastrar o endereço depois, só os avisos novos saem; os anteriores continuam valendo pela consulta.
- Trocar o endereço troca o segredo na hora: o escritório recebe o segredo novo uma vez, e os avisos seguintes já saem assinados com ele. Combine a troca com o escritório para atualizar os dois lados juntos. Avisos que já estavam em nova tentativa seguem para o endereço novo.
Conferir a assinatura
X-Motor-Assinatura: t=<unix>,v1=<hex>. v1 é o HMAC-SHA256, em hexadecimal minúsculo, de t + "." + corpo cru, com o segredo do webhook (o texto exato que apareceu ao gerá-lo, em UTF-8) como chave. Confira sobre os bytes recebidos, antes de interpretar o JSON, e recuse t com mais de 5 minutos de diferença do seu relógio.
Node.js (Express com express.raw({ type: 'application/json' })):
import crypto from 'node:crypto';
export function avisoAutentico(corpoCru, cabecalho, segredo, toleranciaSeg = 300) {
const partes = {};
for (const item of String(cabecalho ?? '').split(',')) {
const i = item.indexOf('=');
if (i > 0) partes[item.slice(0, i).trim()] = item.slice(i + 1).trim();
}
const t = Number(partes.t);
if (!Number.isInteger(t) || !/^[0-9a-f]{64}$/.test(partes.v1 ?? '')) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranciaSeg) return false;
const esperado = crypto
.createHmac('sha256', Buffer.from(segredo, 'utf8'))
.update(Buffer.concat([Buffer.from(`${partes.t}.`, 'utf8'), corpoCru]))
.digest();
return crypto.timingSafeEqual(esperado, Buffer.from(partes.v1, 'hex'));
}
C# (.NET 6 ou mais novo):
using System.Security.Cryptography;
using System.Text;
public static bool AvisoAutentico(byte[] corpoCru, string cabecalho, string segredo, int toleranciaSeg = 300)
{
string? t = null, v1 = null;
foreach (var item in (cabecalho ?? "").Split(','))
{
var i = item.IndexOf('=');
if (i <= 0) continue;
var nome = item.Substring(0, i).Trim();
var valor = item.Substring(i + 1).Trim();
if (nome == "t") t = valor; else if (nome == "v1") v1 = valor;
}
if (t is null || v1 is null || v1.Length != 64 || !long.TryParse(t, out var ts)) return false;
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - ts) > toleranciaSeg) return false;
byte[] recebido;
try { recebido = Convert.FromHexString(v1); } catch (FormatException) { return false; }
var prefixo = Encoding.UTF8.GetBytes(t + ".");
var mensagem = new byte[prefixo.Length + corpoCru.Length];
Buffer.BlockCopy(prefixo, 0, mensagem, 0, prefixo.Length);
Buffer.BlockCopy(corpoCru, 0, mensagem, prefixo.Length, corpoCru.Length);
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(segredo));
return CryptographicOperations.FixedTimeEquals(hmac.ComputeHash(mensagem), recebido);
}