Documentação técnica

Documentação da API de assinatura digital de receitas

Integre seu prontuário, telemedicina ou marketplace à assinatura ICP-Brasil da Prescri. Autenticação por chave, endpoints REST em JSON, sessões reutilizáveis de até 7 dias e webhooks assinados com HMAC-SHA256.

Visão geral

A API recebe um PDF pronto do seu sistema, coleta a autorização do médico no certificado em nuvem (Bird ID ou VIDaaS) e devolve o documento assinado com validade jurídica ICP-Brasil, QR Code de validação e portal público do documento.

Base URL

https://suykokupoevtdfalrzue.supabase.co/functions/v1

Formato

JSON (UTF-8) em request e response. Todas as datas em ISO 8601 UTC.

Fluxo de integração

fluxo
1. POST /api-signatures-sessions-create   -> authorize_url
2. Médico abre authorize_url e autoriza no app do certificado
   (validade: Bird ID até 7 dias · VIDaaS até 12h)
3. POST /api-signatures-create (com session_id)
   -> status "signed" em segundos, sem nova aprovação
4. Webhook signature.completed no seu endpoint (HMAC-SHA256)
5. GET /api-signatures-get  ou  GET /api-signatures-pdf

Sem session_id, o fluxo também funciona: a criação retorna sign_url e o médico autoriza uma assinatura por vez.

Autenticação

Toda requisição exige os headers X-API-Key e X-API-Secret. As credenciais são emitidas por parceiro e podem ser restritas por origem (allowed origins) no caso do SDK.

headers
X-API-Key: pk_live_xxxxxxxxxxxxxxxx
X-API-Secret: sk_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
401 Unauthorized
{
  "error": "Credenciais inválidas"
}
POST
/api-signatures-create

Criar assinatura

Envia o PDF (base64 ou URL) e inicia a assinatura. Com session_id ativo o documento volta já assinado.

Request
POST https://suykokupoevtdfalrzue.supabase.co/functions/v1/api-signatures-create

{
  "provider": "birdid",
  "doctor_cpf": "12345678901",
  "doctor_full_name": "Dra. Ana Souza",
  "doctor_crm": "123456",
  "doctor_crm_uf": "SP",
  "doctor_specialty": "Clínica Médica",
  "doctor_email": "ana.souza@exemplo.com",
  "doctor_phone": "5511999999999",
  "pdf_url": "https://seusistema.com/receita.pdf",
  "document_type": "Receituário Médico",
  "external_id": "atendimento-9876",
  "external_metadata": { "clinica_id": "42" },
  "webhook_url": "https://seusistema.com/webhooks/prescri",
  "redirect_url": "https://seusistema.com/assinatura-ok",
  "session_id": "9f1c1c2e-6b7a-4c6f-9a11-9f2b7f1a0c33",
  "patient": {
    "full_name": "João da Silva",
    "cpf": "98765432100",
    "phone": "5511999998888"
  },
  "delivery": {
    "whatsapp": true,
    "email": true,
    "phone": "5511999998888",
    "email_to": "joao@exemplo.com",
    "clinic_name": "A Clínica CardioVida",
    "system_name": "iMedPro"
  },
  "stamp_options": { "position": "bottom-right", "qrSize": 38, "page": "last" }
}
201 Created — assinatura concluída via sessão
{
  "signature_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "signed",
  "sign_url": "https://prescri.com.br/d/abc123def456",
  "signature_token": "abc123def456"
}
201 Created — aguardando autorização do médico
{
  "signature_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending_authorization",
  "sign_url": "https://prescri.com.br/sign/abc123def456",
  "expires_at": "2026-09-04T15:30:00.000Z"
}

Obrigatórios: provider (birdid | vidaas), doctor_cpf (11 dígitos) e pdf_base64 ou pdf_url (nunca os dois).

Dados do médico (recomendados): doctor_full_name, doctor_crm, doctor_crm_uf, doctor_specialty, doctor_email e doctor_phone. E-mail e celular ajudam a vincular/reutilizar o cadastro e a comunicar com o médico quando necessário.

webhook_url e redirect_url precisam usar HTTPS.

stamp_options: position (top-left padrão), marginX, marginY, qrSize e page — em quais páginas o carimbo aparece: all (padrão, todas), last (só a última), first (só a primeira) ou o número da página (1, 2, ...). Ideal para relatórios e documentos longos.

delivery (opcional): controla o envio automático do documento assinado ao paciente — whatsapp e email (booleanos), phone, email_to e, para personalizar o WhatsApp, patient_name, clinic_name e system_name. Se omitido, segue o padrão da conta em Recursos adicionais. Detalhes e preços na seção Envio ao paciente.

cURL
curl -X POST https://suykokupoevtdfalrzue.supabase.co/functions/v1/api-signatures-create \
  -H "X-API-Key: $PRESCRI_KEY" \
  -H "X-API-Secret: $PRESCRI_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "birdid",
    "doctor_cpf": "12345678901",
    "pdf_url": "https://seusistema.com/receita.pdf",
    "webhook_url": "https://seusistema.com/webhooks/prescri"
  }'
GET
/api-signatures-get?id={signature_id}

Consultar assinatura

Retorna o status atual do documento e o link público quando assinado.

Request
curl "https://suykokupoevtdfalrzue.supabase.co/functions/v1/api-signatures-get?id=550e8400-e29b-41d4-a716-446655440000" \
  -H "X-API-Key: $PRESCRI_KEY" \
  -H "X-API-Secret: $PRESCRI_SECRET"
200 OK
{
  "signature_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "signed",
  "provider": "birdid",
  "pdf_url": "https://prescri.com.br/d/abc123def456",
  "signature_token": "abc123def456",
  "signed_at": "2026-09-03T12:04:11.482Z",
  "expires_at": null,
  "external_id": "atendimento-9876",
  "metadata": { "clinica_id": "42" },
  "error": null,
  "created_at": "2026-09-03T12:03:58.104Z",
  "updated_at": "2026-09-03T12:04:11.482Z"
}

Status possíveis: pending_authorization, authorized, signed, failed, cancelled.

GET
/api-signatures-pdf?id={signature_id}

Baixar PDF assinado

Retorna os bytes do PDF assinado (application/pdf), não JSON.

Request
curl "https://suykokupoevtdfalrzue.supabase.co/functions/v1/api-signatures-pdf?id=550e8400-e29b-41d4-a716-446655440000" \
  -H "X-API-Key: $PRESCRI_KEY" \
  -H "X-API-Secret: $PRESCRI_SECRET" \
  -o receita-assinada.pdf
409 Conflict — ainda não assinado
{
  "error": "Documento ainda não foi assinado",
  "status": "pending_authorization"
}
POST
/api-signatures-cancel

Cancelar assinatura

Cancela uma solicitação que ainda não foi assinada.

Request
curl -X POST "https://suykokupoevtdfalrzue.supabase.co/functions/v1/api-signatures-cancel?id=550e8400-e29b-41d4-a716-446655440000" \
  -H "X-API-Key: $PRESCRI_KEY" \
  -H "X-API-Secret: $PRESCRI_SECRET"
200 OK
{
  "ok": true,
  "status": "cancelled"
}
409 Conflict
{
  "error": "Documento já foi assinado, não pode ser cancelado"
}

Envio automático ao paciente (delivery)

Após a assinatura, a Prescri pode entregar o documento ao paciente por WhatsApp (PDF anexo + link de validação, via template oficial da Meta) e/ou e-mail. O valor é debitado da carteira de créditos da conta: R$ 0,30 por WhatsApp e R$ 0,10 por e-mail. Se o envio falhar, o valor é estornado automaticamente.

  • Controle por documento com o bloco delivery em POST /api-signatures-create: whatsapp e email (booleanos), phone e email_to.
  • Personalização do WhatsApp (opcional, até 60 caracteres): patient_name, clinic_name e system_name. A mensagem oficial mostra paciente, clínica/emissor, sistema, tipo do documento, médico com CRM/UF e o link, com o PDF anexado e botão "Acessar Documento". Sem esses campos usamos o nome do paciente cadastrado, o nome do médico e o nome da sua conta. Texto livre não é permitido pelas regras do WhatsApp oficial.
  • false desativa o envio daquele documento, mesmo com o envio automático ligado na conta. Sem o bloco delivery, vale o padrão configurado no portal em Recursos adicionais.
  • O telefone e o e-mail podem vir de delivery ou do objeto patient (a API atualiza o cadastro do paciente).
  • Sem saldo na carteira, o envio não é feito — mas a assinatura do documento nunca é bloqueada por falta de créditos.
delivery na criação
"delivery": {
  "whatsapp": true,
  "email": false,
  "phone": "5511999998888",
  "email_to": "paciente@exemplo.com",
  "clinic_name": "A Clínica CardioVida",
  "system_name": "iMedPro"
}
POST
/api-signatures-resend

Reenviar documento assinado

Reenvio avulso do documento já assinado ao paciente, por WhatsApp ou e-mail, debitado da carteira de créditos.

Request
POST https://suykokupoevtdfalrzue.supabase.co/functions/v1/api-signatures-resend

{
  "signature_id": "550e8400-e29b-41d4-a716-446655440000",
  "channel": "whatsapp",
  "phone": "5511999998888",
  "idempotency_key": "reenvio-2026-10-15-001"
}

Obrigatórios: signature_id (UUID) e channel (whatsapp | email). Para e-mail, envie email em vez de phone. Sem destinatário informado, usa o cadastro do paciente.

idempotency_key (opcional) evita cobrança duplicada em retries — a mesma chave retorna o resultado original sem novo débito.

200 OK
{
  "ok": true,
  "channel": "whatsapp",
  "delivery_status": "sent",
  "charged_brl": 0.30
}
402 Payment Required — sem saldo
{
  "error": "insufficient_credits",
  "recharge_url": "https://www.prescri.com.br/parceiro?tab=addons"
}
422 Unprocessable — documento ainda não assinado
{
  "error": "Documento ainda não foi assinado"
}
POST
/api-signatures-sessions-create

Criar sessão de assinatura

Cria uma sessão reutilizável. O médico autoriza uma vez e assina várias receitas dentro da validade.

Request
POST https://suykokupoevtdfalrzue.supabase.co/functions/v1/api-signatures-sessions-create

{
  "provider": "birdid",
  "doctor_cpf": "12345678901",
  "session_lifetime_seconds": 604800,
  "redirect_url": "https://seusistema.com/sessao-autorizada"
}
201 Created
{
  "session_id": "9f1c1c2e-6b7a-4c6f-9a11-9f2b7f1a0c33",
  "status": "pending_authorization",
  "authorize_url": "https://prescri.com.br/sessao/Kd82hsMzq1",
  "expires_at": "2026-09-04T07:48:00.000Z",
  "session_lifetime_seconds": 604800,
  "provider": "birdid"
}

Limite de session_lifetime_seconds: 604800 (7 dias) no Bird ID e 43200 (12h) no VIDaaS. Padrão: 43200. O expires_at retornado é a janela de 24h para o médico autorizar; a contagem do lifetime começa na autorização.

GET
/api-signatures-sessions-get?session_id={id}

Consultar sessão

Verifica se a sessão está ativa antes de enviar novas assinaturas.

200 OK
{
  "session_id": "9f1c1c2e-6b7a-4c6f-9a11-9f2b7f1a0c33",
  "status": "active",
  "provider": "birdid",
  "doctor_cpf": "12345678901",
  "doctor_id": "0b2a...",
  "expires_at": "2026-09-10T07:48:00.000Z",
  "authorized_at": "2026-09-03T07:49:12.000Z",
  "signatures_count": 14,
  "last_used_at": "2026-09-03T11:20:03.000Z",
  "session_lifetime_seconds": 604800,
  "created_at": "2026-09-03T07:48:00.000Z"
}

Status: pending_authorization, active, expired, revoked. Sessões vencidas são marcadas automaticamente na consulta.

Assinatura em lote (vários PDFs)

Cada documento ICP-Brasil exige hash criptográfico, carimbo de tempo e código de validação próprios — por isso não existe um endpoint que receba um array de PDFs. O lote é feito com a sessão reutilizável: o médico autoriza uma única vez no app do certificado e o seu sistema envia uma chamada por PDF com o mesmo session_id. Cada resposta volta em 1–2 segundos já com status: "signed".

Exemplo em Node.js
import axios from "axios";
import fs from "fs";

const headers = {
  "x-api-key": process.env.PRESCRI_API_KEY,
  "x-api-secret": process.env.PRESCRI_API_SECRET,
};

async function assinarLote(sessionId, pdfs) {
  for (const item of pdfs) {
    const { data } = await axios.post(
      "https://suykokupoevtdfalrzue.supabase.co/functions/v1/api-signatures-create",
      {
        session_id: sessionId,
        provider: "birdid",
        doctor_cpf: "12345678901",
        pdf_base64: fs.readFileSync(item.caminho).toString("base64"),
        patient: item.paciente,
        delivery: { whatsapp: true },
      },
      { headers }
    );
    console.log("Assinado:", data.signature_id, data.status); // "signed"
  }
}
  • Isolamento de falhas: se um PDF estiver corrompido, só ele falha — os demais continuam sendo assinados.
  • Progresso em tempo real: como cada chamada é independente, sua aplicação pode exibir "assinando 14 de 30…".
  • Paralelismo: envie blocos de 3 a 5 requisições concorrentes para assinar dezenas de documentos em poucos segundos.
  • Consulte a sessão com GET /api-signatures-sessions-get antes do lote para confirmar que está active.
GET
/api-document-types

Listar tipos de documento

Retorna os tipos aceitos em document_type / prescription_type_id.

200 OK
{
  "document_types": [
    { "prescription_type_id": "…", "name": "Atestado Médico", "requires_sncr": false, "sncr_type": null },
    { "prescription_type_id": "…", "name": "Receituário Médico", "requires_sncr": false, "sncr_type": null },
    { "prescription_type_id": "…", "name": "Receituário de Controle Especial", "requires_sncr": true, "sncr_type": "RCE" },
    { "prescription_type_id": "…", "name": "Receita Sujeita a Retenção", "requires_sncr": true, "sncr_type": "RET" },
    { "prescription_type_id": "…", "name": "Notificação de Receita A", "requires_sncr": true, "sncr_type": "NRA" },
    { "prescription_type_id": "…", "name": "Notificação de Receita B", "requires_sncr": true, "sncr_type": "NRB" },
    { "prescription_type_id": "…", "name": "Notificação de Receita B2", "requires_sncr": true, "sncr_type": "NRB2" }
  ]
}

Documentos sem tipo informado assumem Receituário por padrão.

Receitas controladas (RDC 1.000/2025): tipos com requires_sncr: true consomem uma numeração oficial SNCR da carteira do médico. O número volta em sncr_number na criação da assinatura. Se o médico não tiver numeração, a API responde 409 com error: "sncr_no_numbers" e connect_url, onde ele busca números com o gov.br.

Gerar o PDF no modelo Prescri (com a sua logo)

Opcional: em vez de pdf_base64/pdf_url, envie o bloco document em POST /api-signatures-create. A Prescri monta o PDF com a logo e os dados da clínica, o quadro do paciente, o texto e/ou os itens, quebra páginas sozinha, reserva o espaço da assinatura e, em receitas azul/amarela, estampa o número SNCR no topo. Quem já envia PDF pronto não precisa mudar nada.

  • Texto corrido (atestado, laudo, declaração): só body.
  • Receita: só items (nome, quantidade, posologia) — lista numerada.
  • Híbrido: body + items.
  • Logo PNG ou JPG por logo_url (HTTPS) ou logo_base64, até 2 MB, redimensionada para no máx. 50×25 mm. Se a logo falhar, o documento sai sem ela e a resposta traz logo_applied: false.
  • Topo: logo + nome da clínica + médico e CRM. Sem clinic_name, o topo mostra o nome do médico e o CRM.
  • Rodapé (todas as páginas): endereço e telefone na 1ª linha; CNPJ e CNES na 2ª. Campos não enviados são omitidos.
  • clinic_cnpj e clinic_cnes são opcionais e combináveis. Se omitidos, a Prescri usa o CNPJ/CNES cadastrados na sua empresa (quando houver).
  • Não envie document junto com pdf_base64/pdf_url (erro 400).
"document": {
  "title": "Atestado Médico",            // obrigatório
  "body": "Atesto para os devidos fins...", // texto corrido (\n quebra linha)
  "items": [{ "name": "Dipirona 500mg/mL", "quantity": "1 frasco", "dosage": "30 gotas a cada 6h" }],
  "observations": "Retorno em 7 dias",
  "cid10": "J06.9",
  "city": "Recife", "uf": "PE",
  "branding": {
    "logo_url": "https://clinica.com.br/logo.png",   // ou logo_base64
    "logo_position": "left",                         // left | right
    "clinic_name": "Clínica Saúde Total",
    "clinic_cnpj": "12.345.678/0001-90",             // opcional (rodapé)
    "clinic_cnes": "1234567",                        // opcional (rodapé)
    "clinic_phone": "(11) 3456-7890",                // rodapé
    "clinic_address": "Av. Paulista, 1000 - São Paulo/SP" // rodapé
  },
  "patient": { "name": "Maria Silva", "cpf": "12345678900", "birth_date": "1990-05-15", "gender": "feminino", "address": "Rua das Flores, 123 - Recife/PE" }
}

Médico autônomo (sem CNPJ/CNES): envie só clinic_address e clinic_phone — com ou sem clinic_name (nome do consultório). O CPF do médico não é impresso; ele fica no certificado ICP-Brasil.

A resposta traz document_generated: true. Pelo menos body ou items é obrigatório.

Receitas controladas e numeração SNCR (RDC 1.000/2025)

A partir da RDC nº 1.000/2025 da Anvisa, Notificações de Receita (A, B, B2, retinoides e talidomida), Receita de Controle Especial e receitas sujeitas a retenção só são válidas com numeração oficial emitida pelo SNCR. Seu sistema não precisa se integrar ao SNCR — a Prescri faz isso.

Como funciona

  • O médico conecta a conta gov.br dele uma única vez na Prescri e carrega as numerações liberadas pela Vigilância Sanitária na carteira SNCR.
  • Ao criar uma assinatura de tipo controlado, a Prescri reserva automaticamente o próximo número da carteira daquele médico (identificado por doctor_cpf).
  • O número é confirmado como consumido quando a assinatura ICP-Brasil é concluída. Cancelamento ou expiração devolve o número para a carteira.
  • Tipos controlados exigem assinatura qualificada ICP-Brasil (VIDaaS ou Bird ID).

Não existe API Key do SNCR: a Anvisa autoriza por login gov.br do médico, com token de uso único válido por 30 segundos. Por isso o carregamento é feito na Prescri e o parceiro consome apenas a carteira já abastecida.

Requisição — receita controlada
{
  "provider": "birdid",
  "doctor_cpf": "12345678901",
  "document_type": "Notificação de Receita B",
  "patient": { "full_name": "Maria Silva", "cpf": "98765432100" },
  "pdf_base64": "JVBERi0xLjQK…",
  "webhook_url": "https://seusistema.com.br/webhooks/prescri"
}
201 Created — número reservado
{
  "signature_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending_authorization",
  "sncr_number": "2602.6-53.0000001",
  "sncr_type": "NRB"
}
409 Conflict — carteira do médico vazia
{
  "error": "sncr_no_numbers",
  "message": "O médico não possui numeração SNCR disponível para este tipo de receita.",
  "sncr_type": "NRB",
  "connect_url": "https://www.prescri.com.br/medico/perfil#sncr"
}

Ao receber sncr_no_numbers, exiba na sua tela o link de connect_url para o médico recarregar a carteira com o gov.br e repita a chamada. O campo sncr_number também é devolvido em GET /api-signatures-get e no payload do webhook, para gravação no prontuário.

Opção A — Estampa automática no PDF

Envie sncr_stamp em POST /api-signatures-create e a Prescri desenha o quadro "NUMERAÇÃO SNCR" com o número reservado antes da assinatura. Sem sncr_stamp, o PDF segue exatamente como hoje. Posições: top-right (padrão), top-left, top-center, bottom-left, bottom-right, bottom-center ou custom (com x/y em pontos a partir do canto superior esquerdo). page: first, last, all ou número. A resposta traz sncr_stamped: true.

"sncr_stamp": {
  "position": "top-right",   // ou "custom" com "x": 380, "y": 30
  "marginX": 24, "marginY": 24,
  "page": "first",
  "width": 180,              // 120 a 400 pontos
  "uf": "SP"                 // opcional; padrão = doctor_crm_uf
}

Opção B — Pré-reserva do número

Reserve o número, imprima no seu próprio layout e envie o PDF com sncr_reservation_token. A reserva vale 30 minutos (5 a 120 com ttl_minutes); vencida, o número volta para a carteira.

POST /api-sncr-reserve
{ "doctor_cpf": "12345678900", "sncr_type": "NRB", "ttl_minutes": 30 }

201 { "sncr_number": "2602.6-53.0000001", "sncr_type": "NRB",
      "reservation_token": "rsv_…", "expires_at": "2026-09-30T21:30:00Z" }

POST /api-signatures-create
{ …, "document_type": "Notificação de Receita B",
  "sncr_reservation_token": "rsv_…" }

DELETE /api-sncr-reserve?reservation_token=rsv_…   // libera sem usar
// Erros 409: sncr_reservation_expired | _used | _mismatch | _not_found

Consultar saldo antes de emitir

Use para mostrar ao médico, antes de prescrever, se ele tem números disponíveis. can_issue já considera que Controle Especial (RCE) e Retenção (RET) podem ser emitidas sem número.

GET /api-sncr-balance?doctor_cpf=12345678901
X-API-Key: ...  X-API-Secret: ...

{
  "doctor_cpf": "12345678901",
  "doctor_registered": true,
  "connect_url": "https://www.prescri.com.br/medico/perfil#sncr",
  "balances": [
    { "sncr_type": "NRB", "available": 8, "required": true,  "can_issue": true },
    { "sncr_type": "NRA", "available": 0, "required": true,  "can_issue": false },
    { "sncr_type": "RCE", "available": 0, "required": false, "can_issue": true }
  ]
}

Gestão de médicos — /api-doctors

Inclua, liste, edite e inative os médicos autorizados a assinar pela sua integração. Médicos inativos recebem 403 doctor_not_enabled ao tentar assinar. A inativação é lógica: o histórico de documentos é preservado.

Regra de vagas por ciclo (Parceiro Paga)

Vagas em uso = médicos ativos + médicos que já assinaram no ciclo atual. Quem assinou no ciclo mantém a vaga até a renovação, mesmo se for inativado; reativá-lo não consome vaga nova. Médico que nunca assinou no ciclo libera a vaga na hora ao ser inativado. Sem vaga livre, a inclusão retorna 409 doctor_limit_reached.

GET /api-doctors?page=1&limit=50
curl https://suykokupoevtdfalrzue.supabase.co/functions/v1/api-doctors?page=1&limit=50 \
  -H "X-API-Key: SUA_API_KEY" -H "X-API-Secret: SEU_API_SECRET"

{
  "doctors": [{ "id": "…", "doctor_cpf": "12345678909", "full_name": "Dra. Ana Souza",
                "crm": "12345", "crm_uf": "PE", "status": "active" }],
  "total": 1, "page": 1, "active": 1, "max_doctors": 5, "seats_used": 2
}
POST /api-doctors
{
  "doctor_cpf": "12345678909",
  "full_name": "Dra. Ana Souza",
  "crm": "12345",
  "crm_uf": "PE",
  "email": "ana@clinica.com.br",
  "phone": "81999999999"
}
// 201 { "doctor": { "id": "…", "status": "active", ... } }
PATCH /api-doctors/{id}
{ "status": "inactive" }   // ou "active", full_name, crm, crm_uf, email, phone
DELETE /api-doctors/{id}
// Inativa o médico (can_sign = false) e revoga links SNCR pendentes.
// 200 { "doctor": { "id": "…", "status": "inactive" } }
POST /api-sncr-connect-url — link para o médico buscar números no gov.br
{ "doctor_id": "…", "expires_in_hours": 24 }
// 200 { "id": "…", "url": "https://www.prescri.com.br/sncr/conectar/…", "expires_at": "…" }
// 400 doctor_crm_required — preencha CRM/UF do médico antes
// DELETE /api-sncr-connect-url?id=…  revoga o link

Webhooks e validação HMAC

Enviamos um POST para o webhook_url a cada mudança relevante da assinatura. O evento vem no header X-Prescri-Event e no campo event do corpo. São até 4 tentativas (imediata + retries em 2s, 8s e 30s). Responda 2xx rapidamente.

EventostatusQuando
signature.completedsignedDocumento assinado pelo médico.
signature.cancelledcancelledCancelada via api-signatures-cancel. O número SNCR reservado volta para a carteira.
signature.expiredexpiredO médico abriu o link após o prazo de autorização. O número SNCR reservado é liberado.
signature.dispenseddispensedA farmácia confirmou a dispensação no portal Prescri. Dados da farmácia e itens em metadata.
doctor.addedactiveMédico incluído ou reativado (gestão de médicos).
doctor.removedinactiveMédico inativado. Se já assinou no ciclo, a vaga continua ocupada até a renovação.
doctor.limit_reached—Tentativa de incluir médico com todas as vagas do ciclo em uso.
sncr.numbers_added—O médico buscou novos números SNCR pelo gov.br.
sncr.balance_low—Saldo SNCR do médico ficou baixo para um tipo de receita.
Headers
X-Prescri-Event: signature.completed
X-Prescri-Timestamp: 1788280943
X-Prescri-Signature: t=1788280943,v1=9a3f…
X-Prescri-Delivery: 550e8400-e29b-41d4-a716-446655440000
X-Prescri-Attempt: 1
Content-Type: application/json
Payload
{
  "event": "signature.completed",
  "signature_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "signed",
  "external_id": "atendimento-9876",
  "pdf_url": "https://prescri.com.br/d/abc123def456",
  "signed_at": "2026-09-03T12:04:11.482Z",
  "signature_token": "abc123def456",
  "provider": "birdid",
  "error": null,
  "sncr_number": "2602.6-53.0000001",
  "sncr_type": "NRB",
  "metadata": { "clinica_id": "42" }
}
Payload — signature.dispensed
{
  "event": "signature.dispensed",
  "signature_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "dispensed",
  "external_id": "atendimento-9876",
  "sncr_number": "2602.6-53.0000001",
  "sncr_type": "NRB",
  "metadata": {
    "dispensation_id": "8f1c…",
    "dispensed_at": "2026-10-03T14:22:05.120Z",
    "is_partial": false,
    "pharmacy_name": "Farmácia Exemplo",
    "pharmacy_cnpj": "12345678000199",
    "pharmacist_crf": "12345/PE",
    "items": [{ "medication_name": "Clonazepam 2mg", "quantity": 30, "unit": "comprimidos" }]
  }
}
Validação (Node.js)
import crypto from "node:crypto";

// use SEMPRE o corpo cru (raw body), não o JSON re-serializado
app.post("/webhooks/prescri", express.raw({ type: "*/*" }), (req, res) => {
  const header = req.get("X-Prescri-Signature") || "";
  const timestamp = req.get("X-Prescri-Timestamp") || "";
  const received = header.split("v1=")[1] || "";

  const expected = crypto
    .createHmac("sha256", process.env.PRESCRI_API_SECRET)
    .update(`${timestamp}.${req.body.toString("utf8")}`)
    .digest("hex");

  const ok =
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));

  if (!ok) return res.status(401).send("invalid signature");

  const event = JSON.parse(req.body.toString("utf8"));
  // ...processe event.signature_id / event.pdf_url
  res.sendStatus(200);
});

Códigos de erro

HTTPCausaComo resolver
400Campo inválido ou ausente (provider, doctor_cpf, PDF)Confira a mensagem em error e o payload enviado
401Credenciais ausentes ou inválidasRevise X-API-Key / X-API-Secret e o parceiro ativo
402Trial expirado ou limite de assinaturas atingidoSolicite ativação permanente ou aumento de limite
402 insufficient_creditsCarteira sem saldo para envio/reenvio por WhatsApp ou e-mailRecarregue os créditos em API e Integrações › Carteira (link em recharge_url)
403 doctor_not_enabledMédico inativo ou não habilitado neste parceiroReative o médico (PATCH /api-doctors/{id} com status active)
404Assinatura, sessão ou médico não encontradoVerifique o id enviado
409Documento já assinado (cancelamento) ou ainda não assinado (PDF)Consulte o status antes da operação
409 doctor_limit_reachedTodas as vagas do ciclo estão em uso (ativos + quem já assinou no ciclo)Amplie as vagas em API e Integrações ou aguarde o próximo ciclo
409 doctor_already_linkedCPF já cadastrado neste parceiroUse PATCH no médico existente
409 sncr_no_numbersMédico sem numeração SNCR para o tipo controladoEnvie o médico ao connect_url para recarregar a carteira com o gov.br
422 pdf_encrypted / pdf_locked_docmdpPDF com senha ou travado contra novas assinaturas (DocMDP P=1)Envie o PDF sem senha e sem trava; não reenvie o mesmo arquivo
422 provider_rejected_documentO provedor (VIDaaS/BirdID) recusou o documentoVeja provider_response e provider_trace_id; retryable: false
500Erro interno / provedor indisponívelTente novamente; respostas com retryable: true podem ser reenviadas

SDK / iframe embutido

Se preferir não construir o PDF, embuta a experiência completa da Prescri no seu sistema com o SDK em iframe. A referência técnica do SDK (instalação, opções, métodos, seções, eventos e autenticação do médico) fica em página própria.

Documentação do SDK

Integração em poucas linhas de JavaScript, com telas prontas de prescrição, modelos, pacientes e histórico.

Preços e planos

Cobrança por médico ativo no mês, com receitas e chamadas ilimitadas: de R$ 59,90 (1 médico) até R$ 29,90 por médico (101 ou mais). No modelo Médico Paga, seu sistema não paga nada. 10 dias de avaliação com documentos reais.

Ver tabela e simulador de preços

Perguntas frequentes

Pronto para integrar?

Solicite sua chave e teste todos os endpoints por 10 dias, sem custo.

Solicitar API Key
Assinatura Online - API de assinatura eletrônica simples e avançada

Do mesmo grupo Mobipar

Precisa de assinatura simples via API?

Para o paciente aceitar um termo, testemunhas ou contratos sem certificado digital, use a API da Assinatura Online: assinatura eletrônica simples e avançada por link, WhatsApp, e-mail ou SMS.

Conhecer a Assinatura Online

Prescri — assinatura qualificada ICP-Brasil

Médico assina receitas, atestados e notificações SNCR com VIDaaS, BirdID ou A1.

Assinatura Online — assinatura simples

Paciente, testemunhas e empresas assinam por link, sem certificado digital.