Guias de Desenvolvimento

Política e Conformidade

Signing Sessions

Sessão de Confiança

Guias dos SDKs

Sessão de Confiança — Receitas (Copy-Paste)

Exemplos prontos para integrar POST /v1/trust-sessions em backends comuns. Todos assumem que JWT foi obtido via POST /oauth2/token com grant_type=client_credentials.

Receita 1: Node.js (TypeScript) + redirect

import { randomUUID } from 'node:crypto';

async function createTrustSession(opts: {
  action: { type: string; description: string };
  signer: { name: string; cpf: string; email: string; otpChannel: 'email' | 'sms' };
  policyProfile: string;
  metadata?: Record<string, string>;
}) {
  const resp = await fetch('https://api-hml.signdocs.com.br/v1/trust-sessions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SIGNDOCS_JWT}`,
      'Content-Type': 'application/json',
      'X-Idempotency-Key': randomUUID(),
    },
    body: JSON.stringify({
      action: opts.action,
      policy: { profile: opts.policyProfile },
      signer: opts.signer,
      metadata: opts.metadata,
      returnUrl: 'https://app.example.com/done?ts={session_id}',
      cancelUrl: 'https://app.example.com/cancelled',
      locale: 'pt-BR',
      expiresInMinutes: 30,
    }),
  });
  if (!resp.ok) throw new Error(`SignDocs error: ${resp.status} ${await resp.text()}`);
  return resp.json() as Promise<{ sessionId: string; url: string; clientSecret: string }>;
}

// Uso em endpoint Express
app.post('/loans/:id/approve', async (req, res) => {
  const loan = await db.loans.get(req.params.id);
  const { url, clientSecret, sessionId } = await createTrustSession({
    action: {
      type: 'approve_payroll_loan_disbursement',
      description: `Aprovar liberação de empréstimo consignado #${loan.id} no valor de R$ ${(loan.amountCents / 100).toFixed(2)}`,
    },
    signer: { name: loan.borrower.name, cpf: loan.borrower.cpf, email: loan.borrower.email, otpChannel: 'sms' },
    policyProfile: 'BIOMETRIC_SERPRO',
    metadata: { contract_id: loan.id, amount_cents: String(loan.amountCents) },
  });
  await db.loans.update(req.params.id, { pendingTrustSessionId: sessionId });
  res.redirect(`${url}?cs=${clientSecret}`);
});

Receita 2: Python + webhook

import os, uuid, requests

def create_trust_session(action_type, description, signer, profile, metadata=None):
    resp = requests.post(
        "https://api-hml.signdocs.com.br/v1/trust-sessions",
        headers={
            "Authorization": f"Bearer {os.environ['SIGNDOCS_JWT']}",
            "Content-Type": "application/json",
            "X-Idempotency-Key": str(uuid.uuid4()),
        },
        json={
            "action": {"type": action_type, "description": description},
            "policy": {"profile": profile},
            "signer": signer,
            "metadata": metadata or {},
            "returnUrl": "https://app.example.com/done",
            "cancelUrl": "https://app.example.com/cancelled",
            "locale": "pt-BR",
            "expiresInMinutes": 30,
        },
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()

# Receptor de webhook (Flask)
from flask import Flask, request, abort
app = Flask(__name__)

@app.post("/webhooks/signdocs")
def signdocs_webhook():
    body = request.get_json()
    if body["event"] == "trust_session.completed":
        evidence_id = body["data"]["evidenceId"]
        session_id = body["data"]["sessionId"]
        # idempotência: já consumimos esse evidenceId?
        with db.transaction():
            if db.consumed_evidence.exists(evidence_id):
                return "", 200
            session = db.sessions.get(session_id)
            execute_loan_disbursement(session.metadata["contract_id"], evidence_id)
            db.consumed_evidence.insert(evidence_id)
    return "", 200

Receita 3: Go + polling

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "time"
)

type CreateRequest struct {
    Action          map[string]string `json:"action"`
    Policy          map[string]string `json:"policy"`
    Signer          map[string]string `json:"signer"`
    Metadata        map[string]string `json:"metadata,omitempty"`
    Locale          string            `json:"locale"`
    ExpiresInMinutes int              `json:"expiresInMinutes"`
}

type CreateResponse struct {
    SessionID    string `json:"sessionId"`
    URL          string `json:"url"`
    ClientSecret string `json:"clientSecret"`
}

func createTrustSession(req CreateRequest) (*CreateResponse, error) {
    body, _ := json.Marshal(req)
    httpReq, _ := http.NewRequest("POST", "https://api-hml.signdocs.com.br/v1/trust-sessions", bytes.NewReader(body))
    httpReq.Header.Set("Authorization", "Bearer "+os.Getenv("SIGNDOCS_JWT"))
    httpReq.Header.Set("Content-Type", "application/json")
    resp, err := http.DefaultClient.Do(httpReq)
    if err != nil { return nil, err }
    defer resp.Body.Close()
    if resp.StatusCode != 201 {
        return nil, fmt.Errorf("status %d", resp.StatusCode)
    }
    var out CreateResponse
    if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return nil, err }
    return &out, nil
}

func pollUntilDone(sessionID string) (string, error) {
    for i := 0; i < 60; i++ {
        time.Sleep(5 * time.Second)
        req, _ := http.NewRequest("GET",
            "https://api-hml.signdocs.com.br/v1/trust-sessions/"+sessionID+"/status", nil)
        req.Header.Set("Authorization", "Bearer "+os.Getenv("SIGNDOCS_JWT"))
        resp, err := http.DefaultClient.Do(req)
        if err != nil { return "", err }
        var status struct{ Status, EvidenceID string }
        json.NewDecoder(resp.Body).Decode(&status)
        resp.Body.Close()
        switch status.Status {
        case "COMPLETED":
            return status.EvidenceID, nil
        case "FAILED", "EXPIRED", "CANCELLED":
            return "", fmt.Errorf("session terminated: %s", status.Status)
        }
    }
    return "", fmt.Errorf("timeout")
}

Receita 4: PHP (Laravel) — KYC em onboarding

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class KycService {
    public function createSession(User $user, string $accountApplicationId): array {
        $resp = Http::withHeaders([
            'Authorization' => 'Bearer '.config('signdocs.jwt'),
            'X-Idempotency-Key' => (string) Str::uuid(),
        ])->post('https://api-hml.signdocs.com.br/v1/trust-sessions', [
            'action' => [
                'type' => 'kyc_account_opening',
                'description' => "KYC para abertura de conta — {$user->name}, aplicação {$accountApplicationId}",
            ],
            'policy' => ['profile' => 'BIOMETRIC_SERPRO'],
            'signer' => [
                'name' => $user->name,
                'cpf' => $user->cpf,
                'email' => $user->email,
                'phone' => $user->phone,
                'otpChannel' => 'sms',
            ],
            'metadata' => [
                'regulation' => 'BACEN-4753',
                'account_application_id' => $accountApplicationId,
            ],
            'locale' => 'pt-BR',
            'expiresInMinutes' => 60,
        ])->throw()->json();

        $user->kycSessions()->create([
            'session_id' => $resp['sessionId'],
            'status' => 'ACTIVE',
        ]);

        return [$resp['url'].'?cs='.$resp['clientSecret'], $resp['sessionId']];
    }
}

Receita 5: n8n (no-code)

Use o nó SignDocs Brasil publicado em npm (n8n-nodes-signdocs-brasil):

  1. Adicione um nó SignDocs Brasil com credencial OAuth2.
  2. Selecione Resource: Signing Session + Operation: Create Trust Session (variante adicionada na v0.2+).
  3. Preencha Action Type e Action Description no formulário do nó.
  4. Selecione Policy Profile: BIOMETRIC_SERPRO (ou outro biométrico).
  5. Conecte o output a um nó Send Email ou Send WhatsApp que entregue {{$json.url}}?cs={{$json.clientSecret}} ao destinatário.
  6. Adicione um nó Wait + HTTP Request (GET /v1/trust-sessions/{{$json.sessionId}}/status) para polling, OU adicione um trigger SignDocs Webhook que dispare em trust_session.completed.

A versão atual do nó (v0.1.x) já suporta purpose=ACTION_AUTHENTICATION e todos os perfis biométricos no nó Signing Session > Create — basta deixar o campo document vazio. A operação "Create Trust Session" da v0.2+ é apenas um wrapper de UX que esconde o campo de documento e força o purpose.

Receita 6: Zapier (no-code)

Crie um Zap com:

Receita 7: Make.com (Integromat)

Crie um cenário com:

Padrões transversais