Começar

Documentação da API Rubriq

API REST pra integrar o Rubriq em sistemas externos. Crie assinaturas, templates, contatos e webhooks. Toda a superfície da plataforma fica sob /v1/, autenticada por API key.

Base URL
https://api.rubriq.com.br

Mesma URL pra todos os endpoints. HTTPS obrigatório.

Auth
Authorization: Bearer rbq_live_…

API key gerada em Acesso API (planos Pro e Business).

Suporte
contato@rubriq.com.br

Dúvidas, bugs, sugestões — respondemos em até 1 dia útil.

Visão geral em 3 minutos

  1. Gere uma API key no painel da Rubriq (planos Pro e Business).
  2. Crie templates de assinatura (posições de cada signatário no PDF) — opcional, mas acelera.
  3. Faça POST /v1/assinaturas com o PDF em base64 + lista de signatários.
  4. Configure um webhook pra receber assinatura.concluida quando todos assinarem.
  5. Baixe o PDF final selado pelo GET /v1/assinaturas/:id/documento.

Autenticação

A API /v1/ exige uma API key (prefixo rbq_live_…). Gere/gerencie em Acesso API dentro do painel (disponível nos planos Pro e Business). Você pode mandar a key em qualquer um destes headers:

Authorization: Bearer rbq_live_xxxxxxxxxxxxxxxxxxxxxxxx
# ou
x-api-key: rbq_live_xxxxxxxxxxxxxxxxxxxxxxxx

A key é validada a cada requisição. Verificamos que a organização está ativa, que o plano tem a feature api_access, e atualizamos o contador de uso pra rate-limit.

Outras formas de auth (não recomendadas pra integração):
  • Dashboard (rotas sem /v1/): Firebase ID token em Authorization: Bearer … — usado pelo app web do Rubriq.
  • Signer público (/assinaturas/token/:token/…): token opaco enviado por e-mail/WhatsApp pro signatário. Sem auth de header.

Boas práticas

  • Nunca exponha a key no front-end. Trate como senha.
  • Use uma key por integração — facilita revogar uma sem derrubar as outras.
  • Roteie chamadas pelo servidor (não direto do browser do usuário).

Formato de resposta

Toda resposta é JSON com a forma { "ok": boolean, ... }.

Sucesso

{
  "ok": true,
  "data": { ... }
}

Erro

{
  "ok": false,
  "error": "Mensagem amigável",
  "code": "quota_exceeded"
}

Códigos HTTP típicos

StatusSignifica
200Sucesso
400Validação (parâmetro faltando / formato errado)
401API key ausente ou inválida
402Plano não cobre a feature ou cota de IA esgotada
403Organização suspensa
404Recurso não encontrado
409Estado incompatível (ex: cancelar doc já assinado)
429Rate-limit ou cota mensal de assinaturas esgotada
5xxErro interno do servidor

Rate limit & cotas

  • Rate limit: 60 requisições/min por API key. Headers RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset em toda resposta (padrão IETF draft-7 — sem prefixo X-).
  • Cota de assinaturas: definida pelo plano (Pro 20/50/100, Business ilimitado). Estouro: 429 quota_exceeded.
  • Cota de IA: Free = 5 usos/mês; Pro/Business = ilimitado. Endpoints de IA retornam 402 ia_quota_exceeded.
  • Tamanho do PDF: 20 MB.
  • Batch: 100 itens por requisição.

Convenções

Datas

Todas as datas/horas seguem ISO 8601 com timezone: 2026-12-31T23:59:59Z ou 2026-12-31T20:59:59-03:00.

CPF / CNPJ / WhatsApp

Aceitamos com ou sem máscara. Internamente normalizamos pra só dígitos. WhatsApp em formato E.164 com + (+5511999990000).

Posicionamento de assinatura

Coordenadas x e y em % da página (0–100). A âncora é a base-centro: a base do rabisco encosta na linha y%.

IDs

IDs de assinatura, template, pasta, contato são strings opacas. Não tente parsear/inferir significado.

Assinaturas
POST /v1/assinaturas criar uma

Cria uma assinatura. O PDF entra em base64 (até 20 MB). Os signatários recebem o convite por e-mail e/ou WhatsApp automaticamente.

Body

CampoTipoObrigatórioDescrição
documentoNomestringsimNome exibido pro signatário. Inclui no PDF de comprovante.
documentoBase64stringsimPDF em base64 com prefixo data:application/pdf;base64,…
signatariosarraysimLista de signatários. Pelo menos 1. Schema.
templateIdstringUsa posições/papéis salvos no template. Alternativa a posicoesOverride.
posicoesOverridearrayPosições explícitas (ignora template). Schema. Sem templateId e sem posicoesOverride, tentamos detectar o template automaticamente — ver detecção de template. Não detectando nada, a assinatura é criada sem nenhuma posição (0 campos) em vez de dar erro — verifique posicoesDetectadas na resposta.
pastaIdstringMove pra essa pasta após criação.
expiraEmISO dateData limite — após isso o link expira.
tagsstring[]Tags pra busca/filtro.
metodoAutenticacaostring"qualquer" (padrão) ou "icp_brasil" (força certificado digital — Business).

Schema do signatário

CampoTipoNotas
nomestringObrigatório.
cpfstringOpcional. Com ou sem máscara.
emailstringPelo menos email OU whatsapp obrigatório.
whatsappstringE.164: +5511999990001.
rolestringBate com signatarioRole do template. Ex: CONTRATANTE, TESTEMUNHA.

Schema de posição

CampoTipoNotas
paginanumber1-indexed.
xnumber0–100 (% da largura).
ynumber0–100 (% da altura, top-down).
tipostringassinatura, rubrica, nome, cpf, data.
signatarioIdxnumberÍndice no array signatarios (0-based).

Exemplo de requisição

curl -X POST https://api.rubriq.com.br/v1/assinaturas \
  -H "Authorization: Bearer $RBQ_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "documentoNome": "Contrato — Cliente X",
    "documentoBase64": "data:application/pdf;base64,JVBERi0xLjQK...",
    "signatarios": [
      {
        "nome": "Maria Souza",
        "cpf": "12345678909",
        "whatsapp": "+5511999990001",
        "email": "maria@x.com",
        "role": "CONTRATANTE"
      }
    ],
    "templateId": "contrato-cliente",
    "tags": ["vendas", "q1"]
  }'

Resposta de sucesso

{
  "ok": true,
  "data": {
    "id": "abc123",
    "orgId": "org-x",
    "templateId": "contrato-cliente",
    "totalPaginas": 3,
    "posicoesDetectadas": 2,
    "signatarios": [
      {
        "nome": "Maria Souza", "cpf": "12345678909",
        "whatsapp": "+5511999990001", "email": "maria@x.com",
        "role": "CONTRATANTE", "token": "…", "totalPosicoes": 1
      }
    ]
  }
}

signatarios[].token é a credencial do link de assinatura desta pessoa neste documento (o Rubriq já convida por e-mail/WhatsApp automaticamente — o token só importa se você quiser montar seu próprio link/notificação). Trate como uma senha: quem tem o token assina no lugar da pessoa. Fale com o suporte se precisar do formato exato do link.

Erros comuns

StatusCodeQuando
402feature_requires_upgrademetodoAutenticacao=icp_brasil em plano sem ICP-Brasil (Business).
429quota_exceededCota mensal de assinaturas esgotada.
Não existe erro de "posição ausente": sem templateId, sem posicoesOverride e sem detecção, a assinatura é criada normalmente com posicoesDetectadas: 0 — ninguém recebe campo pra assinar. Sempre confira esse número antes de avisar o signatário.
GET /v1/assinaturas listar

Query params

ParamValor
statusPENDENTE | PARCIAL | ASSINADO | CANCELADO
vistaativos (padrão) | arquivados | lixeira
qBusca por nome do documento
tagFiltra por tag exata
cpfCPF de algum signatário (com ou sem máscara)
desde / ateISO date (filtra criadoEm)
limitPadrão 50, máximo 200
curl -H "Authorization: Bearer $RBQ_KEY" \
  "https://api.rubriq.com.br/v1/assinaturas?status=PENDENTE&limit=20"
GET /v1/assinaturas/:id detalhe

Retorna metadados completos da assinatura, incluindo lista de signatários (sem o token opaco do signer).

Query params

ParamValor
incluirEventos1 — adiciona eventos[] na resposta (trilha forense com IP/UA por evento)

Resposta

{
  "ok": true,
  "data": {
    "id": "abc123",
    "documentoNome": "Contrato — Cliente X",
    "status": "ASSINADO",
    "metodoAutenticacao": "qualquer",
    "icpBrasilSelado": false,
    "totalSignatarios": 1,
    "assinados": 1,
    "criadoEm": "2026-05-23T18:00:00Z",
    "assinadoEm": "2026-05-23T19:42:11Z",
    "hashFinal": "ff29dfd84ca10b29c3d8c35ea94eb3ba…",
    "tags": ["vendas", "q1"],
    "pastaId": "pasta-a",
    "envelope": { "id": "6743a949-...", "nome": "Contratação Maria", "total": 2, "indice": 1 },
    "signatarios": [
      {
        "nome": "Maria Souza",
        "cpf": "12345678909",
        "role": "CONTRATANTE",
        "email": "maria@x.com",
        "assinadoEm": "2026-05-23T19:42:11Z",
        "ip": "200.x.x.x",
        "userAgent": "Mozilla/5.0…",
        "assinaturaTipo": "icp_brasil",
        "icpBrasil": {
          "cn": "MARIA SOUZA",
          "ac": "AC SOLUTI Multipla v5",
          "tipo": "PF",
          "numeroSerie": "0x9c6b…",
          "validadeFim": "2027-04-17T23:59:59Z"
        }
      }
    ]
  }
}

envelope só aparece quando o documento faz parte de um envio agrupado (ver Batch) — total é quantos documentos tem o envio, indice a posição deste (a partir de 1).

GET /v1/assinaturas/:id/eventos trilha forense

Endpoint dedicado pra trilha de auditoria completa — todos os eventos no ciclo de vida da assinatura, com IP, user agent e detalhes específicos por tipo. Ideal pra integradores que exibem a timeline forense pro cliente final.

Tipos de evento

TipoDetalhes notáveis
criadausuario, totalSignatarios, templateId
abertosignatarioIdx, ip, userAgent
otp_solicitado / otp_email_solicitado / otp_whatsapp_solicitado / otp_sms_solicitadosignatarioIdx, ip
otp_invalidosignatarioIdx, ip — tentativa falha
assinado / assinado_admin / assinado_icpsignatarioIdx, ip, userAgent, detalhes do cert quando ICP
finalizadodetalhes.hashFinal, detalhes.icpBrasil
email_reenviadosignatarioIdx
canceladausuario
arquivada / restaurada / movido_lixeirausuario, ip

Resposta

{
  "ok": true,
  "data": {
    "total": 6,
    "eventos": [
      { "tipo": "criada", "timestamp": "2026-05-23T18:00:00Z",
        "usuario": { "uid": "abc", "email": "voce@empresa.com" },
        "detalhes": { "totalSignatarios": 1, "templateId": "contrato" } },
      { "tipo": "aberto", "timestamp": "2026-05-23T19:30:11Z",
        "signatarioIdx": 0, "ip": "200.x.x.x", "userAgent": "Mozilla/5.0…" },
      { "tipo": "otp_email_solicitado", "timestamp": "2026-05-23T19:31:05Z",
        "signatarioIdx": 0, "ip": "200.x.x.x" },
      { "tipo": "assinado_icp", "timestamp": "2026-05-23T19:42:11Z",
        "signatarioIdx": 0, "ip": "200.x.x.x", "userAgent": "Mozilla/5.0…",
        "detalhes": { "ac": "AC SOLUTI…", "numeroSerie": "0x9c6b…",
                      "hashCertHex": "abc123…" } },
      { "tipo": "finalizado", "timestamp": "2026-05-23T19:42:13Z",
        "detalhes": { "hashFinal": "ff29dfd…", "icpBrasil": true } }
    ]
  }
}
A mesma trilha pode ser puxada em GET /v1/assinaturas/:id?incluirEventos=1 em uma única chamada se preferir buscar tudo junto.
GET /v1/assinaturas/:id/documento baixar PDF

Por padrão devolve o PDF como stream binário (Content-Type: application/pdf) — pode ser usado direto em <iframe src>, <a download> ou range requests.

Query params

ParamValor
original1 — devolve o PDF antes da estampa (em vez do assinado)
formatobase64 — devolve JSON { pdfBase64 } (formato legado pra compat)

Headers de resposta (binário)

HTTP/2 200
Content-Type: application/pdf
Content-Disposition: inline; filename="Contrato - ASSINADO.pdf"
Cache-Control: private, max-age=300
Content-Length: 158234

<binário do PDF>

Sem assinatura concluída → 409. Use ?original=1 pra baixar antes de assinar.

Validação ICP-Brasil: documentos assinados com certificado digital A1 podem ser validados oficialmente em validar.iti.gov.br (selo "Assinatura Eletrônica Qualificada").
GET /v1/assinaturas/:id/documento/url signed URL

Devolve URL assinada temporária do PDF no Cloud Storage. O client pode usar pra download direto (sem proxy via Rubriq), range requests nativas e UX mais leve.

Query params

ParamValor
original1 — URL do PDF original (em vez do assinado)
ttlTempo de validade da URL em segundos. Default 300, mínimo 60, máximo 3600.

Resposta

{
  "ok": true,
  "data": {
    "pdfUrl": "https://storage.googleapis.com/rubriq-saas.firebasestorage.app/organizations/.../assinado.pdf?X-Goog-Algorithm=...&X-Goog-Signature=...",
    "expiresAt": "2026-05-23T18:45:00Z"
  }
}
POST /v1/assinaturas/batch N documentos

Cria várias assinaturas de uma vez. Máximo 100 itens por requisição.

{
  "items": [
    {
      "documentoNome": "Contrato A",
      "documentoBase64": "data:application/pdf;base64,...",
      "templateId": "contrato",
      "signatarios": [ { "nome": "Maria", ... } ]
    }
  ],
  "signatariosPadrao": [ { "nome": "Maria", ... } ],
  "envelopeNome": "Contratação Maria",
  "anexos": [
    { "documentoNome": "Regulamento interno", "documentoBase64": "data:application/pdf;base64,..." }
  ]
}

signatariosPadrao é usado quando o item não traz signatarios.

Agrupamento em envelope. Com 2 ou mais peças (itens + anexos), o Rubriq agrupa tudo num envelope só como o app: o mesmo signatário recebe um convite só (não um por documento), assina todos de uma vez pelo mesmo link, e o envio ganha ZIP no app. Com 1 item sozinho, o comportamento não muda (documento independente, sem envelope).

envelopeNome (opcional) é o nome do envio mostrado pro signatário. anexos (opcional, máx 20) são { documentoNome, documentoBase64 } que viajam no envio mas ninguém assina (não consomem cota de assinatura) — entram na contagem pra formar o envelope.

Resposta

{
  "ok": true,
  "data": {
    "total": 50,
    "criados": 48,
    "resultados": [
      { "ok": true, "id": "...", "documentoNome": "..." },
      { "ok": false, "error": "...", "documentoNome": "..." }
    ],
    "envelopeId": "6743a949-...",
    "anexos": 1
  }
}

envelopeId sai null quando só 1 peça foi enviada (sem envelope). anexos (contagem) só aparece quando existe envelope. Com envelopeId, GET /v1/assinaturas/:id (ver acima) passa a trazer envelope: { id, nome, total, indice } em cada documento do envio.

POST /v1/assinaturas/split-enviar holerites / folha

Recebe 1 PDF com N páginas + lista de N signatários alinhada por ordem de página. Gera N assinaturas, uma com cada página.

{
  "documentoBase64": "data:application/pdf;base64,...",
  "nomePadrao": "Holerite Out/2026",
  "signatarios": [
    { "nome": "André", "cpf": "...", "whatsapp": "...", "email": "...", "role": "FUNCIONARIO" }
  ],
  "templateId": "holerite-padrao"
}

Ciclo de vida

MétodoEndpointEfeito
DELETE/v1/assinaturas/:idCancela. Manda e-mail de cancelamento pros pendentes.
PATCH/v1/assinaturas/:id/pasta{ "pastaId": "id-ou-null" }
PATCH/v1/assinaturas/:id/tags{ "tags": ["..."] }
PATCH/v1/assinaturas/:id/arquivar{ "arquivar": true|false }
POST/v1/assinaturas/:id/excluirMove pra lixeira (soft-delete).
POST/v1/assinaturas/:id/restaurarRestaura da lixeira.
POST/v1/assinaturas/:id/expurgarExpurgo definitivo (LGPD). Apaga PDFs e doc. Mantém só auditoria. Irreversível.

Por signatário

POST /v1/assinaturas/:id/signatarios/:idx/reenviar-email

Reenvia o e-mail de convite ao signatário pendente.

POST /v1/assinaturas/:id/signatarios/:idx/assinar assinar como admin

Assinatura server-side. Pra integrações que coletam o rabisco no próprio sistema externo.

Atenção: envie ip e userAgent do navegador do signatário no body — sem eles, o carimbo de auditoria registra só o IP do seu servidor (que chamou a API), não o IP probatório do cliente final.

{
  "rabiscoBase64": "data:image/png;base64,iVBORw0KGgo...",
  "rabiscoRubricaBase64": "data:image/png;base64,iVBORw0KGgo...",
  "ip": "200.207.10.42",
  "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ..."
}

rabiscoRubricaBase64 é opcional — só quando o documento tem posições de rubrica além da assinatura completa.

IA
POST /v1/assinaturas/detectar-template

Detecção de template em duas camadas:

  1. Nome do arquivo — grátis, sem IA, sem custo de cota.
  2. Conteúdo do PDF (só se o nome não decidiu e você passou storagePath) — a IA lê o documento e compara com o perfil semântico de cada template. Consome 1 uso de cota de IA; sem cota, sem IA configurada no Rubriq, ou com falha, cai de volta pro resultado da camada 1 sem erro — reconhecer template é conveniência, nunca bloqueia o envio.
CampoTipoObrigatórioDescrição
documentoNomestringsimNome do arquivo — entra na camada 1.
storagePathstringPath do PDF já upado no Storage do Rubriq. Habilita a camada 2. Na prática, você só tem isto se chamar este endpoint depois de já ter subido um documento por outro caminho — normalmente é a própria POST /v1/assinaturas que usa a camada 2 por dentro, automaticamente, quando você não manda templateId/posicoesOverride.
{ "documentoNome": "Termo de Confidencialidade - Empresa XPTO.pdf" }

Resposta

{
  "ok": true,
  "data": {
    "templateId": "nda",
    "nomeTemplate": "NDA",
    "via": "conteudo",
    "aplicar": true,
    "confianca": "alta",
    "motivo": "é um acordo de confidencialidade"
  }
}

data: null quando nada casou. via é "nome" ou "conteudo"; confianca é "alta", "media" ou "baixa".

Na dúvida, não aplique. Com confianca: "media", aplicar vem false — trate como sugestão pra confirmação humana, nunca aplique as posições sem confirmar. Marcação de assinatura no lugar errado é pior que nenhuma marcação.
POST /v1/assinaturas/analisar-upload consome IA

IA lê o PDF e devolve: nome sugerido, problemas de ortografia, qual template casa.

{ "documentoBase64": "data:application/pdf;base64,..." }
POST /v1/assinaturas/sugerir-nome-doc consome IA

IA propõe nome a partir do conteúdo do PDF.

POST /v1/assinaturas/holerites-automatch consome IA

IA lê o CPF de cada página do PDF e devolve pareamento com contatos cadastrados. Atalho perfeito antes de chamar split-enviar.

POST /v1/assinaturas/:id/analisar consome IA

Análise jurídica do documento: resumo, pontos-chave, datas, riscos.

Templates

Listar / detalhar

  • GET /v1/templates — lista resumida.
  • GET /v1/templates/uso{ usado, limite, plano }.
  • GET /v1/templates/:id — detalhe com posições.

Criar / atualizar

  • POST /v1/templates
  • PUT /v1/templates/:id
  • DELETE /v1/templates/:id

Body

{
  "templateId": "holerite",
  "nomeTemplate": "Holerite Mensal",
  "totalPaginas": 1,
  "palavrasChave": ["holerite", "salário"],
  "posicoes": [
    {
      "pagina": 1,
      "x": 50, "y": 90,
      "tipo": "assinatura",
      "signatarioRole": "FUNCIONARIO"
    }
  ],
  "resumoIA": "Recibo mensal de pagamento do funcionário, com descontos e valor líquido.",
  "tipoDocumento": "holerite_pagamento",
  "aliases": ["holerite", "recibo de pagamento", "contracheque"],
  "entidades": ["salário bruto", "descontos", "valor líquido"]
}

resumoIA/tipoDocumento/aliases/entidades são o perfil semântico (o que o documento é) — opcionais, usados pela detecção por conteúdo. Sem eles, o template só entra na camada 1 (nome do arquivo).

palavrasChave só serve pro caminho rápido (nome do arquivo) — prefira termos discriminantes (siglas, número de norma, nome próprio do documento) em vez de termos genéricos do seu ramo, que empatam entre templates parecidos em vez de decidir.

POST /v1/templates/sugerir consome IA

IA lê um PDF modelo e propõe { nome, identificador, palavrasChave[], papeis[], resumoIA, tipoDocumento, aliases[], entidades[] }. Os 4 últimos campos são o perfil semântico acima — mande-os direto no POST/PUT pra o template já nascer com o perfil.

Organização

Pastas

MétodoEndpointBody
GET/v1/pastas
POST/v1/pastas{ nome, cor? }
PATCH/v1/pastas/:id{ nome?, cor? }
DELETE/v1/pastas/:id

Contatos

MétodoEndpointNotas
GET/v1/contatos?q=…Busca opcional.
POST/v1/contatos{ nome, email?, telefone?, cpf?, role? }
PATCH/v1/contatos/:id
DELETE/v1/contatos/:id
POST/v1/contatos/importar{ contatos: [ … ] } — bulk insert.
GET /v1/conta/uso

Retorna o uso atual da sua organização: plano, cota de assinaturas, créditos, cota de IA.

{
  "ok": true,
  "data": {
    "plano": "business",
    "assinaturas": { "usado": 142, "limite": null },
    "creditos": 0,
    "ia": { "ilimitado": true, "limite": null, "usado": 87, "restante": null },
    "periodoStart": "2026-05-01T00:00:00Z",
    "periodoFimEstimado": "2026-06-01T00:00:00Z",
    "subscription": {
      "status": "active",
      "ciclo": "mensal",
      "currentPeriodEnd": "2026-06-01T00:00:00Z",
      "cancelAtPeriodEnd": false
    },
    "planoSource": "paid",
    "grantedBy": null
  }
}

subscription.status é o estado da assinatura Stripe (active|trialing|past_due|canceled); planoSource distingue plano pago (paid) de cortesia/gratuito; grantedBy marca origem de cortesia (ex.: parceria), quando houver.

Webhooks

Configuração

Webhooks notificam seu sistema quando algo acontece (assinatura criada, concluída, cancelada). É o caminho recomendado pra integração assíncrona — evita polling.

MétodoEndpointNotas
GET/v1/webhooks/eventosCatálogo de eventos disponíveis.
GET/v1/webhooksLista endpoints configurados na org.
POST/v1/webhooksCria um endpoint. Retorna secret uma vez só — guarde.
PATCH/v1/webhooks/:id{ url?, eventos?, ativo? }
DELETE/v1/webhooks/:id
GET/v1/webhooks/entregas?limit=50Log de entregas (status, retries).

Eventos disponíveis

EventoQuando
assinatura.criadaNova assinatura criada.
assinatura.concluidaTodos os signatários assinaram.
assinatura.canceladaAssinatura cancelada (admin ou expiração).
assinatura.recusadaUm signatário recusou assinar (motivo opcional).

Payload

Rubriq faz POST application/json pro seu endpoint:

POST /seu-endpoint HTTP/1.1
Content-Type: application/json
X-Rubriq-Event: assinatura.concluida
X-Rubriq-Delivery: 8e6a2bb0-9e1b-4cf9-a4f7-23d0aabd55c1
X-Rubriq-Signature: sha256=ff29dfd84ca10b29c3d8c35ea94eb3ba…

{
  "evento": "assinatura.concluida",
  "em": "2026-05-23T19:42:11Z",
  "dados": {
    "id": "abc123",
    "documentoNome": "Contrato — Cliente X",
    "hashFinal": "ff29dfd84ca10b29c3d8c35ea94eb3ba…",
    "totalSignatarios": 1
  }
}

Validar a assinatura HMAC

X-Rubriq-Signature é HMAC-SHA256 do corpo bruto (antes de qualquer JSON.parse) usando o secret retornado quando você criou o webhook.

const crypto = require('crypto');

function validarWebhook(rawBody, headerSignature, secret) {
  const esperado = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  // crypto.timingSafeEqual previne timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(headerSignature),
    Buffer.from(esperado),
  );
}

// Express
app.post('/webhook-rubriq', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-rubriq-signature'];
  if (!validarWebhook(req.body, sig, process.env.RUBRIQ_WEBHOOK_SECRET)) {
    return res.status(401).end();
  }
  const evento = JSON.parse(req.body.toString());
  // ... processar evento
  res.status(200).end();
});

Retry & entregas

  • 3 tentativas in-process com back-off 0s / 2s / 5s.
  • Pare confirmando com HTTP 2xx.
  • Em qualquer outro status (ou timeout 10s), tentamos de novo.
  • Após 3 falhas, o evento vai pro log de entregas com status failed — consultável em /v1/webhooks/entregas.
  • Idempotência: garanta no seu lado processar duas vezes o mesmo X-Rubriq-Delivery sem efeito colateral.
Público (sem auth)
GET /assinaturas/verificar/:id

Qualquer um pode confirmar que um documento foi assinado no Rubriq batendo nessa URL com o ID — sem API key. Retorna metadados públicos + hash (não expõe dados pessoais completos).

Pra validação criptográfica oficial pela ITI (selo "Assinatura Eletrônica Qualificada"), envie o PDF assinado em validar.iti.gov.br.

Referência

Códigos de erro úteis

Duas formas do erro, dependendo de onde ele nasce: falha de autenticação/plano (checada antes de qualquer rota rodar) manda o slug direto no campo error, sem code separado. Falha de negócio (dentro de uma rota) manda error como mensagem humana e code como o slug. Confira sempre os dois — código que só olha code quebra silenciosamente numa falha de auth.
SlugOnde vemSignifica
api_key_missingerrorHeader de auth ausente.
api_key_invaliderrorKey inválida ou revogada.
org_not_founderrorA org da key não existe mais.
org_suspendederrorConta suspensa.
feature_requires_upgradeerror + campo featurePlano não cobre a feature.
ia_quota_exceededcodeCota mensal de IA esgotada.
quota_exceededcodeCota mensal de assinaturas esgotada.
template_limitcodeLimite de templates do plano atingido.

Exemplo end-to-end

Fluxo completo em Node.js: cria assinatura, recebe webhook, baixa PDF assinado.

// 1) CRIAR ASSINATURA
import fs from 'node:fs';

const pdf = fs.readFileSync('./contrato.pdf').toString('base64');

const criada = await fetch('https://api.rubriq.com.br/v1/assinaturas', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.RBQ_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    documentoNome: 'Contrato — Cliente X',
    documentoBase64: `data:application/pdf;base64,${pdf}`,
    templateId: 'contrato-cliente',
    signatarios: [
      {
        nome: 'Maria Souza',
        cpf: '12345678909',
        whatsapp: '+5511999990001',
        email: 'maria@x.com',
        role: 'CONTRATANTE',
      },
    ],
    tags: ['vendas'],
  }),
}).then((r) => r.json());

console.log('Criada:', criada.data.id);
const id = criada.data.id;


// 2) AGUARDAR WEBHOOK 'assinatura.concluida' (configurado previamente)
//    → seu endpoint recebe POST com { dados: { id, ... } }


// 3) BAIXAR PDF FINAL (no handler do webhook) — resposta é BINÁRIA por
//    padrão; ?formato=base64 mantém o formato { pdfBase64 } se preferir.
const res = await fetch(
  `https://api.rubriq.com.br/v1/assinaturas/${id}/documento`,
  { headers: { Authorization: `Bearer ${process.env.RBQ_KEY}` } },
);
const buffer = Buffer.from(await res.arrayBuffer());
fs.writeFileSync('./contrato-assinado.pdf', buffer);

Changelog

DataMudança
2026-08-14 Novo evento de webhook assinatura.recusada. Disparado quando um signatário recusa assinar (motivo opcional no payload, em dados.motivo). A recusa de qualquer signatário trava o documento inteiro (não vai mais receber assinatura.concluida).
2026-08-06 Envelope multi-documento no POST /v1/assinaturas/batch. Com 2 ou mais peças (itens + anexos) o envio agora é agrupado como no app: um convite só por signatário, mesmo link pra assinar tudo. Novos campos: envelopeNome e anexos no body; envelopeId e anexos (contagem) na resposta; GET /v1/assinaturas/:id ganhou envelope: { id, nome, total, indice }. Com 1 item só, nada muda. Novo endpoint GET /v1/assinaturas/envelopes/:envelopeId/links devolve o link de assinatura de cada pessoa do envelope, pra quem quer entregar pelo próprio canal em vez do convite automático do Rubriq.
2026-08-06 Detecção de template por conteúdo. POST /v1/assinaturas/detectar-template ganhou o campo opcional storagePath (habilita a camada 2: a IA lê o PDF quando o nome do arquivo não decide) e a resposta agora traz via/aplicar/confianca/motivo. POST/PUT /v1/templates e POST /v1/templates/sugerir ganharam o perfil semântico (resumoIA, tipoDocumento, aliases, entidades). Também corrigidos: resposta de POST /v1/assinaturas (o exemplo anterior não correspondia ao formato real) e remoção de um código de erro que nunca existiu (posicao_ausente).
2026-05-23 GET /v1/assinaturas/:id agora aceita ?incluirEventos=1 e inclui campos extras nos signatários (ip, userAgent, assinaturaTipo, icpBrasil). Novo endpoint dedicado GET /v1/assinaturas/:id/eventos com trilha forense.
2026-05-23 GET /v1/assinaturas/:id/documento agora retorna PDF binário por padrão (Content-Type: application/pdf). Use ?formato=base64 pra manter o formato legado. Novo GET /v1/assinaturas/:id/documento/url devolve signed URL temporária pra download direto do Storage.
2026-05-21Suporte a certificado ICP-Brasil A1 (assinatura qualificada) via Web Crypto no signer. Validável no gov.br.
2026-05-21metodoAutenticacao no POST /v1/assinaturas: o criador pode exigir ICP-Brasil.
2026-05-20CPF do signatário virou opcional. Email OU WhatsApp obrigatório.
2026-05-15Webhooks GA: assinatura.criada/concluida/cancelada com HMAC-SHA256.
2026-05-01Endpoints de holerites com auto-match por CPF via IA.

Algo confuso, faltando ou errado? Manda email pra Fale Conosco — respondemos em até 1 dia útil.