Pular para o conteúdo principal

Verificacao de Assinatura

Toda notificacao de webhook enviada pela TapSign inclui uma assinatura digital no header X-Webhook-Signature. Essa assinatura garante que a requisicao foi realmente enviada pela TapSign e que o conteudo nao foi adulterado.

Como funciona

  1. A TapSign gera um HMAC-SHA256 do corpo da requisicao usando o secret do webhook
  2. O hash e enviado no header X-Webhook-Signature
  3. Sua aplicacao recalcula o hash e compara com o recebido
Sempre valide a assinatura

Nunca processe um webhook sem verificar a assinatura. Sem essa validacao, qualquer pessoa poderia enviar requisicoes falsas para sua URL.

Header de assinatura

X-Webhook-Signature: sha256=a1b2c3d4e5f6...

O valor segue o formato sha256={hash}, onde {hash} e o HMAC-SHA256 do corpo da requisicao em hexadecimal.

Exemplos de codigo

Node.js

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}

// Uso com Express
app.post('/webhooks/tapsign', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = req.body.toString();

if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Assinatura invalida');
}

const event = JSON.parse(payload);
console.log('Evento recebido:', event.event);

// Processar evento...

res.status(200).send('OK');
});

Java / Kotlin

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

fun verifyWebhookSignature(payload: String, signature: String, secret: String): Boolean {
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(secret.toByteArray(), "HmacSHA256"))

val expectedHash = mac.doFinal(payload.toByteArray())
.joinToString("") { "%02x".format(it) }

val expectedSignature = "sha256=$expectedHash"

return signature == expectedSignature
}

// Uso com Spring Boot
@RestController
class WebhookController {

@Value("\${tapsign.webhook.secret}")
lateinit var webhookSecret: String

@PostMapping("/webhooks/tapsign")
fun handleWebhook(
@RequestBody payload: String,
@RequestHeader("X-Webhook-Signature") signature: String
): ResponseEntity<String> {
if (!verifyWebhookSignature(payload, signature, webhookSecret)) {
return ResponseEntity.status(401).body("Assinatura invalida")
}

val event = objectMapper.readTree(payload)
println("Evento recebido: ${event["event"].asText()}")

// Processar evento...

return ResponseEntity.ok("OK")
}
}

Python

import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected_hash = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()

expected_signature = f"sha256={expected_hash}"

return hmac.compare_digest(signature, expected_signature)

# Uso com Flask
from flask import Flask, request, abort

app = Flask(__name__)

@app.route('/webhooks/tapsign', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_data()

if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
abort(401, 'Assinatura invalida')

event = request.get_json()
print(f"Evento recebido: {event['event']}")

# Processar evento...

return 'OK', 200
Use comparacao segura

Sempre utilize funcoes de comparacao em tempo constante (timingSafeEqual, compare_digest) para evitar ataques de timing.


Politica de retentativas

Quando a entrega de um webhook falha (qualquer resposta diferente de HTTP 2xx), a TapSign reenvia automaticamente seguindo este cronograma:

TentativaIntervalo
1aImediata
2a1 minuto
3a5 minutos
4a30 minutos
5a2 horas
Dead-letter queue

Apos 5 tentativas sem sucesso, o evento e movido para uma dead-letter queue. Voce pode reenviar manualmente pelo endpoint de reenvio de entrega ou pelo painel da TapSign.

Boas praticas

1. Responda rapidamente

Sua aplicacao deve retornar HTTP 200 o mais rapido possivel (idealmente em menos de 5 segundos). Processe o evento de forma assincrona se necessario.

// Bom: responde imediatamente e processa depois
app.post('/webhooks/tapsign', (req, res) => {
res.status(200).send('OK');
processEventAsync(req.body); // fila, worker, etc.
});

2. Seja idempotente

O mesmo evento pode ser entregue mais de uma vez (em caso de retentativa). Use o campo id do evento para evitar processamento duplicado.

async function processEvent(event) {
const alreadyProcessed = await db.webhookEvents.findById(event.id);
if (alreadyProcessed) return; // ja processado, ignorar

await db.webhookEvents.create({ id: event.id, processedAt: new Date() });
// processar evento...
}

3. Sempre valide a assinatura

Nunca confie cegamente em uma requisicao recebida. Sempre verifique o header X-Webhook-Signature antes de processar o evento.

4. Use HTTPS

Sua URL de webhook deve obrigatoriamente usar HTTPS para proteger os dados em transito.

5. Registre tudo em log

Mantenha logs de todos os webhooks recebidos para facilitar a depuracao de problemas. Registre pelo menos: event.id, event.event, timestamp e o resultado do processamento.