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.
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}`);
});
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
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")
}
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']];
}
}
Use o nó SignDocs Brasil publicado em npm (n8n-nodes-signdocs-brasil):
Action Type e Action Description no formulário do nó.Policy Profile: BIOMETRIC_SERPRO (ou outro biométrico).{{$json.url}}?cs={{$json.clientSecret}} ao destinatário./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.
Crie um Zap com:
Action Type, Action Description, Signer Name/CPF/Email, Policy Profile: BIOMETRIC.{{Trust_Session_URL}}?cs={{Client_Secret}}.trust_session.completed e dispara um Zap de continuação (ex: registra a aprovação no CRM, libera a operação, envia mensagem de confirmação).Crie um cenário com:
trust_session.completed e processa o resultado (atualiza CRM, libera operação, gera nota fiscal).X-Idempotency-Key em toda criação. Use um UUID v4 derivado de uma chave de negócio (ex: SHA-256 de loan_id + version), assim retries não criam sessões duplicadas.metadata. Carregue as chaves do seu sistema interno aqui — é o caminho mais rápido para correlacionar webhooks com seus recursos.returnUrl com {session_id} como placeholder; o SignDocs faz a interpolação ao redirecionar — útil para roteamento em SPA.BIOMETRIC e suba para BIOMETRIC_SERPRO apenas quando precisar do cross-check governamental — BIOMETRIC puro é ~3x mais rápido e barato.