# Validar HMAC-SHA256

Todo webhook enviado pela Movvia inclui o header `x-movvia-signature` com a assinatura HMAC-SHA256 do payload. Sempre valide a assinatura antes de processar.

## Como a assinatura é gerada


```
HMAC-SHA256(secret, payload_utf8) → hex string
```

Onde:

- `secret` é o `webhook_secret` fornecido no onboarding.
- `payload_utf8` é o body JSON bruto (bytes), não o objeto deserializado.


Use o body bruto
Deserializar e re-serializar o JSON pode alterar a ordem das chaves e quebrar a verificação. Leia o body como bytes antes de qualquer parse.

## Node.js


```typescript
import crypto from 'crypto';
import express from 'express';

const app = express();

// Capturar body como Buffer antes do parse
app.use(express.json({
  verify: (req: any, _res, buf) => { req.rawBody = buf; }
}));

function verificarAssinatura(
  rawBody: Buffer,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

app.post('/webhook', (req: any, res) => {
  const sig = req.headers['x-movvia-signature'] as string;

  if (!sig || !verificarAssinatura(req.rawBody, sig, process.env.MV_WEBHOOK_SECRET!)) {
    return res.status(401).json({ erro: 'Assinatura inválida' });
  }

  // processar req.body com segurança
  console.log('Evento válido:', req.body.evento);
  res.status(200).send('OK');
});
```

## Python


```python
import hashlib
import hmac
import os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ['MV_WEBHOOK_SECRET'].encode()

def verificar_assinatura(payload: bytes, signature: str) -> bool:
    expected = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature, expected)

@app.route('/webhook', methods=['POST'])
def webhook():
    sig = request.headers.get('x-movvia-signature', '')
    payload = request.get_data()  # body bruto

    if not verificar_assinatura(payload, sig):
        abort(401)

    evento = request.json
    print(f"Evento válido: {evento['evento']}")
    return 'OK', 200
```

## Go


```go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
    "os"
)

func verificarAssinatura(payload []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expected))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    sig := r.Header.Get("x-movvia-signature")
    body, _ := io.ReadAll(r.Body)

    if !verificarAssinatura(body, sig, os.Getenv("MV_WEBHOOK_SECRET")) {
        http.Error(w, "Assinatura inválida", http.StatusUnauthorized)
        return
    }

    w.WriteHeader(http.StatusOK)
}
```

## Erros comuns

| Problema | Causa provável | Solução |
|  --- | --- | --- |
| Assinatura sempre inválida | Body deserializado antes da verificação | Usar body bruto (bytes) |
| Comparação insegura | `==` em vez de `timingSafeEqual`/`compare_digest` | Sempre usar comparação em tempo constante |
| Secret incorreto | `webhook_secret` diferente do `client_secret` | São credenciais distintas — verificar o correto no onboarding |


Testar localmente
Use o endpoint de simulação do sandbox para gerar eventos reais com assinatura válida. Veja o [Quickstart](/parceiros/tutorials/quickstart).