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/v1Formato
JSON (UTF-8) em request e response. Todas as datas em ISO 8601 UTC.
Fluxo de integração
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-pdfSem 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.
X-API-Key: pk_live_xxxxxxxxxxxxxxxx
X-API-Secret: sk_live_xxxxxxxxxxxxxxxx
Content-Type: application/json{
"error": "Credenciais inválidas"
}/api-signatures-createCriar assinatura
Envia o PDF (base64 ou URL) e inicia a assinatura. Com session_id ativo o documento volta já assinado.
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" }
}{
"signature_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "signed",
"sign_url": "https://prescri.com.br/d/abc123def456",
"signature_token": "abc123def456"
}{
"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 -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"
}'/api-signatures-get?id={signature_id}Consultar assinatura
Retorna o status atual do documento e o link público quando assinado.
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"{
"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.
/api-signatures-pdf?id={signature_id}Baixar PDF assinado
Retorna os bytes do PDF assinado (application/pdf), não JSON.
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{
"error": "Documento ainda não foi assinado",
"status": "pending_authorization"
}/api-signatures-cancelCancelar assinatura
Cancela uma solicitação que ainda não foi assinada.
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"{
"ok": true,
"status": "cancelled"
}{
"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
deliveryemPOST /api-signatures-create:whatsappeemail(booleanos),phoneeemail_to. - Personalização do WhatsApp (opcional, até 60 caracteres):
patient_name,clinic_nameesystem_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. falsedesativa o envio daquele documento, mesmo com o envio automático ligado na conta. Sem o blocodelivery, vale o padrão configurado no portal em Recursos adicionais.- O telefone e o e-mail podem vir de
deliveryou do objetopatient(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": {
"whatsapp": true,
"email": false,
"phone": "5511999998888",
"email_to": "paciente@exemplo.com",
"clinic_name": "A Clínica CardioVida",
"system_name": "iMedPro"
}/api-signatures-resendReenviar documento assinado
Reenvio avulso do documento já assinado ao paciente, por WhatsApp ou e-mail, debitado da carteira de créditos.
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.
{
"ok": true,
"channel": "whatsapp",
"delivery_status": "sent",
"charged_brl": 0.30
}{
"error": "insufficient_credits",
"recharge_url": "https://www.prescri.com.br/parceiro?tab=addons"
}{
"error": "Documento ainda não foi assinado"
}/api-signatures-sessions-createCriar sessão de assinatura
Cria uma sessão reutilizável. O médico autoriza uma vez e assina várias receitas dentro da validade.
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"
}{
"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.
/api-signatures-sessions-get?session_id={id}Consultar sessão
Verifica se a sessão está ativa antes de enviar novas assinaturas.
{
"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".
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-getantes do lote para confirmar que estáactive.
/api-document-typesListar tipos de documento
Retorna os tipos aceitos em document_type / prescription_type_id.
{
"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) oulogo_base64, até 2 MB, redimensionada para no máx. 50×25 mm. Se a logo falhar, o documento sai sem ela e a resposta trazlogo_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_cnpjeclinic_cnessão opcionais e combináveis. Se omitidos, a Prescri usa o CNPJ/CNES cadastrados na sua empresa (quando houver).- Não envie
documentjunto compdf_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.
{
"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"
}{
"signature_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending_authorization",
"sncr_number": "2602.6-53.0000001",
"sncr_type": "NRB"
}{
"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_foundConsultar 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.
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
}{
"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", ... } }{ "status": "inactive" } // ou "active", full_name, crm, crm_uf, email, phone// Inativa o médico (can_sign = false) e revoga links SNCR pendentes.
// 200 { "doctor": { "id": "…", "status": "inactive" } }{ "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 linkWebhooks 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.
| Evento | status | Quando |
|---|---|---|
signature.completed | signed | Documento assinado pelo médico. |
signature.cancelled | cancelled | Cancelada via api-signatures-cancel. O número SNCR reservado volta para a carteira. |
signature.expired | expired | O médico abriu o link após o prazo de autorização. O número SNCR reservado é liberado. |
signature.dispensed | dispensed | A farmácia confirmou a dispensação no portal Prescri. Dados da farmácia e itens em metadata. |
doctor.added | active | Médico incluído ou reativado (gestão de médicos). |
doctor.removed | inactive | Mé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. |
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{
"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" }
}{
"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" }]
}
}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
| HTTP | Causa | Como resolver |
|---|---|---|
| 400 | Campo inválido ou ausente (provider, doctor_cpf, PDF) | Confira a mensagem em error e o payload enviado |
| 401 | Credenciais ausentes ou inválidas | Revise X-API-Key / X-API-Secret e o parceiro ativo |
| 402 | Trial expirado ou limite de assinaturas atingido | Solicite ativação permanente ou aumento de limite |
| 402 insufficient_credits | Carteira sem saldo para envio/reenvio por WhatsApp ou e-mail | Recarregue os créditos em API e Integrações › Carteira (link em recharge_url) |
| 403 doctor_not_enabled | Médico inativo ou não habilitado neste parceiro | Reative o médico (PATCH /api-doctors/{id} com status active) |
| 404 | Assinatura, sessão ou médico não encontrado | Verifique o id enviado |
| 409 | Documento já assinado (cancelamento) ou ainda não assinado (PDF) | Consulte o status antes da operação |
| 409 doctor_limit_reached | Todas 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_linked | CPF já cadastrado neste parceiro | Use PATCH no médico existente |
| 409 sncr_no_numbers | Médico sem numeração SNCR para o tipo controlado | Envie o médico ao connect_url para recarregar a carteira com o gov.br |
| 422 pdf_encrypted / pdf_locked_docmdp | PDF 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_document | O provedor (VIDaaS/BirdID) recusou o documento | Veja provider_response e provider_trace_id; retryable: false |
| 500 | Erro interno / provedor indisponível | Tente 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çosPerguntas frequentes
Pronto para integrar?
Solicite sua chave e teste todos os endpoints por 10 dias, sem custo.
Solicitar API KeyDo 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.
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.