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).

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-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.