Skip to main content
O CNP (Custom Node Protocol) é o protocolo HTTP usado pelo Triglit para se comunicar com seus servidores e executar custom nodes. Ele define como o Triglit envia requisições e como você deve responder.

O que é o CNP?

O CNP é um protocolo simples baseado em HTTP/JSON que permite:
  • ✅ Executar lógica customizada nos seus próprios servidores
  • ✅ Receber dados do workflow (inputs, config, metadata)
  • ✅ Retornar resultados para o workflow
  • ✅ Autenticação segura via HMAC SHA256
  • ✅ Suporte a heartbeat para verificação de saúde

Por que usar CNP?

Execute qualquer lógica nos seus servidores, sem limitações do Triglit.
Conecte workflows diretamente com sua infraestrutura existente.
Autenticação via HMAC garante que apenas o Triglit pode chamar seus endpoints.

Como Funciona

Fluxo de Execução

Workflow → Custom Node → Triglit → CNP Request → Seu Servidor

Workflow ← Custom Node ← Triglit ← CNP Response ← Seu Servidor
  1. Workflow executa: Um workflow chega em um custom node
  2. Triglit prepara requisição: Monta payload e gera assinatura HMAC
  3. Requisição HTTP: POST para seu endpoint CNP
  4. Você valida e processa: Valida assinatura e executa lógica
  5. Resposta: Retorna resultado ou erro
  6. Workflow continua: Triglit usa o resultado para continuar o workflow

Configuração

1. Configurar Endpoint no Triglit

No painel do Triglit, configure seu endpoint CNP:
  1. Acesse ConfiguraçõesCNP
  2. Configure o Custom Nodes Endpoint: https://api.seudominio.com/triglit/cnp
  3. O secret será gerado automaticamente (ou você pode usar um existente)

2. Implementar Servidor CNP

Seu servidor deve expor um endpoint POST que aceita requisições CNP:
import express from 'express';
import { Triglit, type CNPRequestBody } from 'triglit';

const app = express();
app.use(express.json());

const CNP_SECRET = process.env.TRIGLIT_CNP_SECRET!;
const triglit = new Triglit({ apiKey: 'your-api-key' });

// Endpoint CNP
app.post('/triglit/cnp', async (req, res) => {
  try {
    const signature = req.headers['x-triglit-signature'] as string;
    
    if (!signature) {
      return res.status(401).json({
        status: 'failed',
        error: 'Missing signature header'
      });
    }
    
    // Tipar o body usando o tipo do SDK
    const body = req.body as CNPRequestBody<Record<string, unknown>, Record<string, unknown>>;
    
    // Serializar payload como JSON string (igual ao que o Triglit envia)
    const payload = JSON.stringify(body);
    
    // Validar assinatura usando o SDK (recomendado)
    const isValid = await triglit.customNodes.validateCNPSignature(
      payload,
      signature,
      CNP_SECRET
    );
    
    if (!isValid) {
      return res.status(401).json({
        status: 'failed',
        error: 'Invalid signature'
      });
    }
    
    // Heartbeat
    if (body.type === 'heartbeat') {
      return res.status(200).json({ status: 'ok' });
    }
    
    // Executar custom node (body.type === 'run' neste ponto)
    if (body.type === 'run') {
      const { nodeType, inputs, config } = body.payload;
      
      // Sua lógica customizada aqui
      const result = await executeCustomNode(nodeType, inputs, config);
      
      return res.status(200).json({
        status: 'completed',
        outputs: result
      });
    }
    
  } catch (error) {
    return res.status(500).json({
      status: 'failed',
      error: (error as Error).message
    });
  }
});

app.listen(3000);

Estrutura da Requisição

Quando o Triglit executa um custom node, ele envia uma requisição POST com:

Headers

Content-Type: application/json
X-Triglit-Signature: sha256=<hmac_sha256_base64>

Body (Payload)

{
  "nodeType": "process-payment",
  "runId": "run_123",
  "tenantId": "tenant_abc",
  "subTenantId": "sub_xyz",
  "inputs": {
    "orderId": "order_456",
    "amount": 100.00,
    "currency": "USD"
  },
  "config": {
    "gateway": "stripe",
    "autoCapture": true
  },
  "metadata": {
    "timestamp": "2024-01-15T10:00:00Z",
    "version": "1.0.0"
  }
}

Campos do Payload

  • nodeType: Tipo do custom node sendo executado
  • runId: ID da execução (run) atual
  • tenantId: ID do tenant
  • subTenantId: ID do sub-tenant (opcional)
  • inputs: Dados de entrada do workflow (output do node anterior)
  • config: Configuração do node no workflow
  • metadata: Metadados da execução (timestamp, versão do node)

Estrutura da Resposta

Sucesso

{
  "status": "completed",
  "outputs": {
    "paymentId": "pay_123",
    "status": "succeeded",
    "amount": 100.00
  }
}

Erro

{
  "status": "failed",
  "error": "Payment gateway unavailable"
}

Heartbeat

{
  "status": "ok"
}

Autenticação HMAC

Como Funciona

  1. Triglit serializa o payload como JSON string
  2. Gera assinatura HMAC SHA256 usando o secret do tenant
  3. Envia no header X-Triglit-Signature no formato sha256=<base64>
  4. Você valida recalculando a assinatura e comparando (ou usando o SDK TypeScript)

Algoritmo de Validação

A assinatura é enviada no formato sha256=<base64>:
// Usando o SDK TypeScript (recomendado)
import { Triglit } from 'triglit';

const triglit = new Triglit({ apiKey: 'your-api-key' });
const isValid = await triglit.customNodes.validateCNPSignature(
  payload,
  signature,
  cnpSecret
);
// Validação manual
function validateSignature(payload, signature, secret) {
  // 1. Calcular HMAC SHA256
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(payload, 'utf8');
  // 2. Formato: sha256=<base64>
  const expectedSignature = `sha256=${hmac.digest('base64')}`;
  
  // 3. Comparação time-safe
  return timingSafeEqual(signature, expectedSignature);
}
Formato da assinatura:
  • Algoritmo: HMAC SHA256
  • Encoding: Base64 (com prefixo sha256=)
  • Header: X-Triglit-Signature
  • Valor: String no formato sha256=<base64> (ex: sha256=a1b2c3d4e5f6...)
Sempre use comparação time-safe (timing-safe comparison) para validar assinaturas. Comparações normais podem ser vulneráveis a timing attacks.

Heartbeat

O Triglit pode enviar requisições de heartbeat para verificar se seu servidor está online:
{
  "type": "heartbeat"
}
Responda com:
{
  "status": "ok"
}
Heartbeats não requerem processamento de lógica. Apenas valide a assinatura e retorne { status: "ok" }.

Timeout e Retries

Timeout

Cada custom node tem um timeout configurável (padrão: 30 segundos). Se seu servidor não responder dentro do timeout, o Triglit considera como falha.

Retries

O Triglit retenta automaticamente em caso de falha:
  • Máximo de tentativas: Configurável por node (padrão: 3)
  • Backoff: Exponencial entre tentativas
  • Falhas que disparam retry: Timeout, erro 5xx, erro de rede

Boas Práticas

Sempre valide a assinatura antes de processar. Use comparação time-safe.
Torne seus handlers idempotentes usando runId para evitar processamento duplicado.
Retorne rapidamente (dentro do timeout). Processe operações longas de forma assíncrona.
Sempre retorne erros estruturados com mensagens claras.
Logue todas as requisições usando runId para rastreabilidade.
Use sempre HTTPS em produção para proteger dados em trânsito.

Exemplo Completo

Servidor CNP Completo

import express from 'express';
import { Triglit, type CNPRequestBody } from 'triglit';

const app = express();
app.use(express.json());

const CNP_SECRET = process.env.TRIGLIT_CNP_SECRET!;
const triglit = new Triglit({ apiKey: 'your-api-key' });

async function executeCustomNode(nodeType: string, inputs: any, config: any) {
  // Exemplo: Processar pagamento
  if (nodeType === 'process-payment') {
    const { orderId, amount } = inputs;
    const { gateway } = config;
    
    // Simular processamento
    const paymentId = `pay_${Date.now()}`;
    
    return {
      paymentId,
      status: 'succeeded',
      amount,
      gateway
    };
  }
  
  // Outros tipos de nodes...
  throw new Error(`Unknown node type: ${nodeType}`);
}

app.post('/triglit/cnp', async (req, res) => {
  try {
    const signature = req.headers['x-triglit-signature'] as string;
    
    if (!signature) {
      return res.status(401).json({
        status: 'failed',
        error: 'Missing signature'
      });
    }
    
    // Tipar o body usando o tipo do SDK
    const body = req.body as CNPRequestBody<Record<string, unknown>, Record<string, unknown>>;
    const payload = JSON.stringify(body);
    
    // Validar assinatura usando o SDK
    const isValid = await triglit.customNodes.validateCNPSignature(
      payload,
      signature,
      CNP_SECRET
    );
    
    if (!isValid) {
      return res.status(401).json({
        status: 'failed',
        error: 'Invalid signature'
      });
    }
    
    // Heartbeat
    if (body.type === 'heartbeat') {
      return res.json({ status: 'ok' });
    }
    
    // Executar node (body.type === 'run' neste ponto)
    if (body.type === 'run') {
      const { nodeType, inputs, config, runId } = body.payload;
      
      console.log(`[CNP] Executing ${nodeType} for run ${runId}`);
      
      const outputs = await executeCustomNode(nodeType, inputs, config);
      
      return res.json({
        status: 'completed',
        outputs
      });
    }
    
  } catch (error) {
    console.error('[CNP] Error:', error);
    return res.status(500).json({
      status: 'failed',
      error: (error as Error).message
    });
  }
});

app.listen(3000, () => {
  console.log('CNP server running on port 3000');
});

Limitações

  • Timeout máximo: 5 minutos por requisição (configurável por node)
  • Tamanho de payload: 1MB máximo
  • Retries: Máximo de 10 tentativas (configurável por node)

Troubleshooting

Erro: Invalid Signature

  1. Verifique se o secret está correto
  2. Certifique-se de serializar o payload exatamente como recebido
  3. Use comparação time-safe
  4. Verifique se está usando o formato correto: sha256=<base64> (não hexadecimal)
  5. Recomendado: Use o SDK TypeScript com validateCNPSignature para evitar erros de implementação

Erro: Timeout

  1. Otimize seu código para responder rapidamente
  2. Processe operações longas de forma assíncrona
  3. Aumente o timeout do node se necessário

Erro: Connection Refused

  1. Verifique se seu servidor está acessível
  2. Confirme que o endpoint está correto no Triglit
  3. Verifique firewall e configurações de rede
Use o painel do Triglit para testar seu endpoint CNP antes de usar em produção. O painel oferece ferramentas de validação e teste.