> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wincred.digital/llms.txt
> Use this file to discover all available pages before exploring further.

# Assinatura do webhook

> Como verificar o X-Wincred-Signature de cada notificação.

Toda notificação, inclusive o `ping` de teste, traz o header:

```text theme={null}
X-Wincred-Signature: t=<epoch>,v1=<hex>
```

* `t` é o epoch em segundos **do envio**. Ele muda a cada tentativa, enquanto o corpo e o `event_id` permanecem os mesmos.
* `v1` é o HMAC-SHA256, em hexadecimal minúsculo, de `t + "." + corpo`, calculado com o segredo do seu cadastro.

Verifique **toda** requisição antes de confiar nela e recuse as que não passarem na verificação. É essa assinatura, e não o IP de origem, que garante que a notificação veio da Wincred.

**1. Leia o corpo cru**

Calcule sobre os **bytes exatos** recebidos, antes de converter o JSON. Reserializar altera os bytes e invalida a assinatura.

**2. Recalcule o HMAC**

`HMAC-SHA256(segredo, t + "." + corpo)`, em hexadecimal.

**3. Compare em tempo constante**

Use a comparação de tempo constante da sua linguagem, em vez de `==`, para evitar ataques de temporização.

**4. Confira a tolerância**

Recuse se a diferença entre o instante atual e `t` passar de **5 minutos**. Essa janela impede que uma requisição capturada seja reutilizada mais tarde.

```go Go theme={null}
import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"strconv"
	"strings"
	"time"
)

const toleranciaAssinatura = 5 * time.Minute

func assinaturaValida(segredo []byte, header string, corpoCru []byte, agora time.Time) bool {
	var t, v1 string
	for _, parte := range strings.Split(header, ",") {
		chave, valor, _ := strings.Cut(parte, "=")
		switch chave {
		case "t":
			t = valor
		case "v1":
			v1 = valor
		}
	}
	epoch, err := strconv.ParseInt(t, 10, 64)
	if err != nil || v1 == "" {
		return false
	}
	if agora.Sub(time.Unix(epoch, 0)).Abs() > toleranciaAssinatura {
		return false
	}

	mac := hmac.New(sha256.New, segredo)
	mac.Write([]byte(t + "."))
	mac.Write(corpoCru)
	recebido, err := hex.DecodeString(v1)
	return err == nil && hmac.Equal(mac.Sum(nil), recebido)
}
```

```python Python theme={null}
import hashlib, hmac, time

TOLERANCIA_SEGUNDOS = 300


def assinatura_valida(segredo: str, header: str, corpo_cru: bytes, agora: float | None = None) -> bool:
    try:
        partes = dict(item.split("=", 1) for item in header.split(","))
        t = int(partes["t"])
        v1 = partes["v1"]
    except (KeyError, ValueError):
        return False

    agora = time.time() if agora is None else agora
    if abs(agora - t) > TOLERANCIA_SEGUNDOS:
        return False

    esperado = hmac.new(segredo.encode(), f"{t}.".encode() + corpo_cru, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperado, v1)
```

```javascript Node.js theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

const TOLERANCIA_SEGUNDOS = 300;

export function assinaturaValida(segredo, header, corpoCru, agora = Date.now() / 1000) {
  const partes = Object.fromEntries(
    header.split(",").map((item) => {
      const i = item.indexOf("=");
      return [item.slice(0, i), item.slice(i + 1)];
    }),
  );
  const t = Number(partes.t);
  if (!Number.isInteger(t) || typeof partes.v1 !== "string") return false;
  if (Math.abs(agora - t) > TOLERANCIA_SEGUNDOS) return false;

  const esperado = createHmac("sha256", segredo).update(`${t}.`).update(corpoCru).digest();
  const recebido = Buffer.from(partes.v1, "hex");
  return recebido.length === esperado.length && timingSafeEqual(esperado, recebido);
}
```

## Vetor de teste

Use estes valores de exemplo, que não pertencem ao seu cadastro, para conferir a sua implementação. Passe como "agora" um instante de até 5 minutos depois de `t`, por exemplo `1791000100`.

| Item | Valor |
| - | - |
| Segredo | `segredo-de-exemplo` |
| `t` | `1791000000` |
| Corpo | `{"event_id":"7d0f3a6e-2b1c-4d5e-8f90-1a2b3c4d5e6f","event_type":"ping","occurred_at":"2026-10-06T08:00:00-03:00","data":null}` |
| `v1` esperado | `268d99bd3c44cc56167e6a5fdf4b52ec342c186db64bc7d50f591d2ebff86a58` |

No terminal:

```bash theme={null}
printf '%s' '1791000000.{"event_id":"7d0f3a6e-2b1c-4d5e-8f90-1a2b3c4d5e6f","event_type":"ping","occurred_at":"2026-10-06T08:00:00-03:00","data":null}' \
  | openssl dgst -sha256 -hmac 'segredo-de-exemplo'
```

Em seguida, valide de ponta a ponta com `POST /webhooks/test`: o `ping` chega com a assinatura real, calculada com o seu segredo. Veja [Testando em dev](https://docs.wincred.digital/api-do-parceiro/testando-em-dev.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.