Webhooks
Receber eventos
A Oneos avisa o seu sistema a cada passo da operação, com um POST assinado para a URL que você cadastrar.
Configurar o endpoint
Em Minha empresa › Webhooks, na plataforma:
- Informe a URL do seu endpoint.
- Escolha os eventos que quer receber.
- Guarde o signing secret (
whsec_…) — ele aparece uma única vez.
Regras da URL:
- HTTPS na porta 443, com endereço público — IPs internos ou privados são recusados.
- Sem usuário e senha embutidos na URL.
- Redirecionamento não é seguido: responder
3xxconta como falha.
Cada empresa tem um endpoint, na matriz. Você pode pausar as entregas, rotacionar o secret, enviar um evento de teste e ver o histórico na aba Entregas.
Formato da entrega
POST /webhooks/oneos HTTP/1.1
Host: erp.suaempresa.com.br
content-type: application/json
x-oneos-signature: t=1789482001,v1=5f0c…9a2e
x-oneos-event-id: 9f8e7d6c-1b2a-4c3d-8e9f-0a1b2c3d4e5f
x-oneos-event-type: pre_authorization.created
{
"id": "9f8e7d6c-1b2a-4c3d-8e9f-0a1b2c3d4e5f",
"type": "pre_authorization.created",
"createdAt": "2026-09-15T13:00:01.000Z",
"data": {
"anticipationId": 48213,
"status": "IN_PRE_AUTH",
"origin": "PRE_AUTH",
"invoice": {
"amount": 18400,
"dueDate": "2026-11-30",
"number": "48211"
},
"netAmount": null,
"payer": {
"cnpj": "44555666000199",
"name": "Construtora Exemplo LTDA"
},
"supplier": {
"cnpj": "11222333000181",
"name": "Fornecedor Exemplo ME"
},
"externalReference": "PED-4581",
"reason": null,
"stage": null
}
}
Headers
| Header | Conteúdo |
|---|---|
x-oneos-signature | Assinatura t=<segundos>,v1=<hmac>. Veja Verificar assinatura. |
x-oneos-event-id | O id do evento, igual ao do corpo. |
x-oneos-event-type | O type do evento, igual ao do corpo. |
Envelope
-
idstring obrigatórioIdentificador único do evento. É o mesmo em todas as tentativas e reenvios — use para deduplicar.
-
typeenum obrigatórioTipo do evento, do catálogo.
-
createdAtstring obrigatórioMomento em que o evento aconteceu, ISO 8601 em UTC.
-
dataobject obrigatórioConteúdo do evento. O formato depende da família do tipo.
Eventos de pré-autorização e antecipação
O data de pre_authorization.* e anticipation.* tem sempre este formato:
-
anticipationIdinteger obrigatórioO
idda operação — o mesmo da resposta de criação. -
statusenum obrigatórioStatus da operação depois do evento.
-
originenum obrigatórioPRE_AUTHquando nasceu de pré-autorização;DIRECTquando o fornecedor antecipou direto. -
invoiceobject obrigatórioDados da nota.
-
amountinvoice. number obrigatórioValor bruto.
-
dueDateinvoice. string obrigatórioVencimento,
AAAA-MM-DD. -
numberinvoice. string | null obrigatórioNúmero da nota.
-
netAmountnumber | null obrigatórioValor líquido ao fornecedor, ou
nullenquanto não foi calculado. -
payerobject obrigatórioA construtora (tomadora) da operação.
-
cnpjpayer. string obrigatórioCNPJ da construtora.
-
namepayer. string obrigatórioRazão social da construtora.
-
supplierobject obrigatórioO fornecedor da operação.
-
cnpjsupplier. string obrigatórioCNPJ do fornecedor.
-
namesupplier. string | null obrigatórioNome do fornecedor, ou
null. -
externalReferencestring | null obrigatórioO identificador do seu ERP, ou
null. -
reasonstring | null opcionalMotivo de encerramento, quando houver — por exemplo
Cancelada via integraçãoouPré-autorização expirada. Senãonull. -
stagestring | null opcionalEtapa em que a operação foi encerrada:
PRE_AUTH,REQUESTouSYSTEM. Senãonull. -
extensionobject opcionalSó nos eventos
anticipation.extension_*: a prorrogação do boleto. -
idextension. integer obrigatórioIdentificador da prorrogação.
-
newDueDateextension. string obrigatórioNovo vencimento do boleto.
-
totalAmountextension. number obrigatórioValor total do boleto prorrogado.
Os outros formatos de data estão no catálogo de eventos.
Seu endpoint, em três regras
- Verifique a assinatura com o corpo bruto, antes de qualquer parse.
- Responda 2xx em até 10 segundos e processe depois, numa fila.
- Deduplique pelo
id— o mesmo evento pode chegar mais de uma vez, e fora de ordem.
import express from 'express'
import { createHmac, timingSafeEqual } from 'node:crypto'
const SECRET = process.env.ONEOS_WEBHOOK_SECRET // whsec_...
const TOLERANCE_SECONDS = 300
function isValidSignature(rawBody, header) {
const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')))
const timestamp = Number(parts.t)
if (!Number.isInteger(timestamp) || !parts.v1) return false
if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false
const expected = createHmac('sha256', SECRET).update(`${timestamp}.${rawBody}`).digest()
const received = Buffer.from(parts.v1, 'hex')
return expected.length === received.length && timingSafeEqual(expected, received)
}
const app = express()
// corpo BRUTO: o HMAC é sobre os bytes exatos que chegaram
app.post('/webhooks/oneos', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString('utf8')
if (!isValidSignature(rawBody, req.get('x-oneos-signature') ?? '')) {
return res.sendStatus(400)
}
const event = JSON.parse(rawBody)
res.sendStatus(200) // responda rápido: o limite é 10 s
enqueue(event) // processe fora do request, deduplicando por event.id
})
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
SECRET = os.environ["ONEOS_WEBHOOK_SECRET"].encode() # whsec_...
TOLERANCE_SECONDS = 300
app = Flask(__name__)
def is_valid_signature(raw_body: bytes, header: str) -> bool:
parts = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
try:
timestamp = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - timestamp) > TOLERANCE_SECONDS:
return False
expected = hmac.new(SECRET, f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
@app.post("/webhooks/oneos")
def oneos_webhook():
raw_body = request.get_data() # bytes exatos, antes de qualquer parse
if not is_valid_signature(raw_body, request.headers.get("x-oneos-signature", "")):
abort(400)
event = json.loads(raw_body)
enqueue(event) # deduplique por event["id"]
return "", 200
<?php
const TOLERANCE_SECONDS = 300;
function oneos_signature_is_valid(string $rawBody, string $header, string $secret): bool
{
$parts = [];
foreach (explode(',', $header) as $pair) {
[$key, $value] = array_pad(explode('=', $pair, 2), 2, '');
$parts[trim($key)] = trim($value);
}
if (!isset($parts['t'], $parts['v1']) || !ctype_digit($parts['t'])) {
return false;
}
if (abs(time() - (int) $parts['t']) > TOLERANCE_SECONDS) {
return false;
}
$expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
return hash_equals($expected, $parts['v1']);
}
$rawBody = file_get_contents('php://input'); // corpo bruto
$header = $_SERVER['HTTP_X_ONEOS_SIGNATURE'] ?? '';
if (!oneos_signature_is_valid($rawBody, $header, getenv('ONEOS_WEBHOOK_SECRET'))) {
http_response_code(400);
exit;
}
$event = json_decode($rawBody, true);
http_response_code(200);
// enfileire $event e deduplique por $event['id']
Quem recebe
Eventos de pré-autorização e antecipação vão para a construtora e para o fornecedor da operação — cada um no seu próprio endpoint, se tiver. Eventos de nota fiscal e de ordem de pagamento vão só para a empresa dona.