Integração · Cursor
Assinatura eletrônica no seu projeto com o Cursor
O prompt orienta o agente do Cursor a manter todas as chamadas no servidor, validar webhooks e tratar erros pelos códigos estáveis da API.
curl -X POST /v1/signature-requests
201 Created · 312 ms
"status": "SENT", "signingUrl": "…/s/9xq4m"
webhook signer.signed → 200 OK
Antes de tudo: a chave fica no servidor
Onde fica a chave: ASSINAPI_SECRET_KEY só em variáveis de ambiente do servidor. Em Next.js, nunca com prefixo NEXT_PUBLIC_.
Como funciona
- 1
Gere a chave de Sandbox e adicione ao .env do servidor.
- 2
Cole o prompt no chat do Cursor (modo agente) na raiz do projeto.
- 3
O agente cria o serviço server-side e as rotas internas que o seu frontend chama.
- 4
Teste com o OTP 123456 e os CPFs de teste do Sandbox.
- 5
Troque para a chave ask_live_… quando estiver pronto para Produção.
Prompt e código prontos
Prompt para o Cursor
Implemente a integração com a AssinAPI neste projeto. Analise a stack existente e siga as convenções do código.
## Objetivo
Implementar: criação de envelope; upload de PDF; cadastro de signatário (nome, e-mail, CPF); envio para assinatura e armazenamento dos signingLinks; endpoint de webhook para envelope.completed (com validação HMAC); download do documento assinado e do certificado de evidências.
## Configuração
- Base URL: https://api.assinapi.com.br/v1
- Autenticação: header `Authorization: Bearer ${ASSINAPI_SECRET_KEY}`
- Leia a chave SOMENTE da variável de ambiente ASSINAPI_SECRET_KEY no servidor.
- Leia o segredo do webhook de ASSINAPI_WEBHOOK_SECRET.
- Chaves ask_test_… = Sandbox (OTP de teste 123456, CPF de teste 529.982.247-25). Chaves ask_live_… = Produção.
## Regras de segurança (obrigatórias)
- NUNCA exponha ASSINAPI_SECRET_KEY no frontend, no navegador, em app mobile, em logs ou no repositório.
- Crie uma camada server-side chamada `assinapi.service` que concentra todas as chamadas à AssinAPI.
- O frontend conversa apenas com o seu backend.
- Não registre CPF, telefone, códigos OTP ou links de assinatura em logs.
## Endpoints
- POST https://api.assinapi.com.br/v1/envelopes
body: { "title": string, "message"?: string, "signatureLevel"?: "SIMPLE"|"ADVANCED", "expirationDays"?: number, "externalId"?: string }
201 → { "id": "env_…", "status": "DRAFT", … }
- POST https://api.assinapi.com.br/v1/envelopes/{envelopeId}/documents
multipart/form-data com o campo "file" (PDF) — ou JSON { "contentBase64": string, "filename": string }
201 → envelope com documents[].sha256
- POST https://api.assinapi.com.br/v1/envelopes/{envelopeId}/signers
body: { "name": string, "email": string, "cpf": string, "birthDate"?: "AAAA-MM-DD", "phone"?: "+5511999999999" (envie sempre que tiver: usado no WhatsApp, planos pagos), "authenticationMethod"?: "EMAIL_LINK"|"EMAIL_OTP"|"WHATSAPP_LINK"|"WHATSAPP_OTP" (WHATSAPP_* só nos planos pagos; no Free → 402 WHATSAPP_REQUIRES_PAID_PLAN. Omitido = padrão do projeto: simples → só link por e-mail; avançada → código pelo WhatsApp nos planos pagos, por e-mail no Free), "signingOrder"?: number }
201 → { "id": "sgn_…", "status": "PENDING", "cpfMasked": "***.456.789-**" }
- POST https://api.assinapi.com.br/v1/envelopes/{envelopeId}/send
body: { "notify"?: boolean } (notify=false → você envia o link por outro canal)
200 → { "status": "SENT", "signingLinks": [{ "signerId", "name", "signingUrl", "expiresAt" }] }
Os signingLinks só aparecem NESTA resposta: salve-os se precisar.
- GET https://api.assinapi.com.br/v1/envelopes/{envelopeId}
200 → { "status": "DRAFT"|"SENT"|"IN_PROGRESS"|"COMPLETED"|"DECLINED"|"EXPIRED"|"CANCELLED", "signers": [{ "status" }] }
- GET https://api.assinapi.com.br/v1/envelopes/{envelopeId}/download (somente após COMPLETED)
200 → arquivo PDF assinado, com relatório de assinaturas e auditoria (IP, localização, dispositivo, trilha). ?type=certificate → certificado de evidências.
Baixe e armazene no seu storage.
Atalho recomendado (uma chamada só): POST https://api.assinapi.com.br/v1/signature-requests
body: { "title": string, "document": { "filename": string, "contentBase64": string }, "externalId"?: string, "signers": [{ "name", "email", "cpf", "birthDate"?, "fields"?: [{ "anchor": "{{assinatura_cliente}}" } | { "page": "last", "x": 60, "y": 80 }], "requireDocumentPhoto"?: boolean, "requireSelfie"?: boolean }], "redirectUrl"?: string, "reminderIntervalDays"?: number }
201 → { "id": "sigreq_…", "envelopeId": "env_…", "status": "SENT", "signingUrl": string, "signers": [{ "id", "signingUrl" }] } — salve o id (sigreq_…).
GET https://api.assinapi.com.br/v1/signature-requests/{id} → { "status", "completed": boolean, "nextStep": string, "downloads": { "signedPdfUrl" } | null }
GET https://api.assinapi.com.br/v1/signature-requests/{id}/download → PDF assinado. POST …/{id}/remind (lembrete) e POST …/{id}/cancel.
## Idempotência e retries
- Envie `Idempotency-Key: <uuid>` em todo POST. Em retry (timeout, 429, 5xx) reenvie com a MESMA chave.
- Em 429 respeite o header Retry-After e use backoff exponencial.
## Tratamento de erros
Erros têm o formato { "error": { "code", "message", "hint", "docs", "requestId" } }. Trate pelo `code` (estável), exiba `message` ao usuário quando fizer sentido e registre o `requestId` para suporte.
Códigos comuns: VALIDATION_FAILED, CPF_REQUIRED, INVALID_CPF, ENVELOPE_INCOMPLETE, INVALID_STATE_TRANSITION, INVALID_API_KEY, RATE_LIMITED.
## Webhook
- Registre o endpoint: POST https://api.assinapi.com.br/v1/webhooks { "url": "https://…/api/webhooks/assinapi", "events": ["envelope.completed", "signer.signed"] } → guarde o "secret" retornado (exibido uma vez).
- Headers recebidos: X-Signature (v1=<hex>), X-Event-Id, X-Timestamp.
- Valide: HMAC_SHA256(ASSINAPI_WEBHOOK_SECRET, `${X-Event-Id}.${X-Timestamp}.${corpo_bruto}`) === X-Signature (comparação em tempo constante).
- Rejeite X-Timestamp com mais de 5 minutos; deduplique por X-Event-Id; responda 2xx rápido.
- Todo evento: data.envelope = { "id" (env_…), "signatureRequestId" (sigreq_…), "status", "externalId" }.
- Ao receber envelope.completed (o PDF já está pronto), marque o registro como FINALIZADO e baixe o PDF em GET /signature-requests/{signatureRequestId}/download.
- signer.signed = assinatura parcial; signer.declined / envelope.expired / envelope.cancelled = encerrado sem assinatura; signer.identity_submitted = fotos aguardando aprovação.
## Entregáveis
- `assinapi.service` com funções tipadas para cada operação.
- Persistir o id (sigreq_…) e o envelopeId junto ao registro do seu sistema, e enviar o seu id em externalId.
- Variáveis de ambiente documentadas (.env.example sem valores reais).
- Testes da camada de serviço com a API mockada.Exemplos apontam para o Sandbox (https://api.assinapi.com.br/v1). Em Produção, troque apenas a chave (ask_live_…); o endereço é o mesmo.
Perguntas frequentes
Perguntas técnicas.
Qual a diferença para o prompt do Claude Code?
O conteúdo técnico é o mesmo (endpoints, segurança, webhooks); muda só a forma de instruir cada ferramenta.
Como meu sistema fica sabendo que o documento foi assinado?
Por webhooks assinados com HMAC-SHA256 (eventos como signer.signed e envelope.completed) ou consultando GET /v1/signature-requests/:id, que devolve o status e o próximo passo em português.
Como o signatário se autentica? Dá para usar WhatsApp?
WhatsApp está nos planos pagos. Simples: por padrão só o link por e-mail (EMAIL_LINK). Avançada (CPF e data de nascimento conferidos): nos planos pagos, link por e-mail + código pelo WhatsApp (WHATSAPP_OTP) ou só WhatsApp (WHATSAPP_LINK); no Free, link e código por e-mail (EMAIL_OTP), e pedir WhatsApp devolve 402 WHATSAPP_REQUIRES_PAID_PLAN. Envie o celular em phone (+5511999999999) e, se quiser, defina o padrão no projeto.
Consigo testar sem custo?
Sim. O Sandbox é gratuito: chaves ask_test_…, código de confirmação fixo 123456 e CPFs de teste. Documentos de Sandbox não têm validade e não consomem a franquia do plano.
Primeira assinatura no Sandbox em menos de 10 minutos.
Sem cartão de crédito. Sandbox gratuito; você só paga por documentos enviados em Produção.