> For the complete documentation index, see [llms.txt](https://docs.caf.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.caf.io/caf-api/caf-api-pt-br/connect/webhook/signature.md).

# Assinatura

## Visão geral

Este documento fornece orientações sobre assinaturas de mensagens de webhook e como validar o `X-Caf-Signature` cabeçalho incluído em todas as solicitações de webhook feitas pelos serviços da Certta.

## Assinaturas de mensagens

Como as URLs de webhook ficam expostas à internet, sua aplicação precisa de um mecanismo seguro para verificar se as solicitações realmente vêm da Certta. Implementamos um cabeçalho de validação de assinatura para esse propósito, que chamamos de assinatura da mensagem.

{% hint style="warning" %}
Apesar de ser uma validação interna para sua integração, rejeitar solicitações com assinaturas inválidas faz parte do processo de validação de webhook na Certta. Podemos enviar aleatoriamente eventos com assinaturas inválidas para verificar se sua integração continua atendendo aos nossos critérios de validação. De qualquer forma, essa validação é do seu interesse para evitar fraudes. Mantemos trilhas de auditoria dos eventos entregues, das tentativas de entrega e dos eventos descartados.
{% endhint %}

## Cabeçalho de assinatura

Cada solicitação de webhook inclui um `X-Caf-Signature` cabeçalho com o seguinte formato:

```
X-Caf-Signature: 5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

O cabeçalho contém o valor da assinatura HMAC SHA-256 gerado a partir do corpo bruto da solicitação usando seu segredo de cliente.

## Como validar

A Certta implementa um mecanismo de código de autenticação de mensagem por hash com chave (HMAC) com SHA256 para gerar uma assinatura para cada mensagem enviada, com a codificação final em hexadecimal.

Essa assinatura é gerada usando o segredo de cliente da sua aplicação (o mesmo usado para gerar tokens), encontrado no Trust, a plataforma que gerencia as configurações. A assinatura é enviada por meio do `X-Caf-Signature` cabeçalho em cada `HTTP` solicitação.

Para validar essa assinatura, sua integração deve:

1. Gerar o HMAC da mensagem recebida usando seu segredo (armazenado em um local seguro)
2. Compará-lo com a assinatura recebida usando um algoritmo de comparação segura

### Considerações importantes de segurança

{% hint style="info" %}
Como campos podem ser adicionados a qualquer evento a qualquer momento sem que isso represente uma mudança incompatível, a validação da mensagem deve ser feita antes que o conteúdo seja transformado em um objeto de linguagem. Isso significa usar o array de bytes do corpo "como está" para gerar a assinatura de comparação, sem qualquer transformação.

Isso também é importante porque, ao lidar com `JSON`s, `{"prop1": "value1", "prop2": "value2"}` é equivalente a `{"prop2": "value2", "prop1": "value1"}` para parsers/encoders, já que a ordem das propriedades não faz parte de uma `JSON` definição, mas os arrays de bytes formados pelos dois objetos são diferentes. Além disso, alguns caracteres podem ser codificados de forma diferente dependendo da biblioteca ou da linguagem utilizada.
{% endhint %}

## Exemplos de implementação

Muitas linguagens de programação incluem implementações seguras de HMAC em suas bibliotecas padrão:

* Python: [módulo hmac](https://docs.python.org/3/library/hmac.html)
* Node.js: [classe crypto.Hmac](https://nodejs.org/api/crypto.html#class-hmac)
* Ruby: [OpenSSL::HMAC](https://ruby-doc.org/stdlib-2.4.0/libdoc/openssl/rdoc/OpenSSL/HMAC.html)
* Go: [pacote crypto/hmac](https://pkg.go.dev/crypto/hmac)
* Java: [javax.crypto.Mac](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/crypto/Mac.html)

### Variações de formato

Todos esses exemplos são válidos para o mesmo JSON, mas com formatações diferentes, e sua integração deve oferecer suporte a todos eles:

#### Sem espaços ou quebras de linha

```json
{"id":"evt_123456789","source":"TRANSACTION","specversion":"1.0","type":"TRANSACTIONSTATUSUPDATEDEVENT","time":"2025-07-08T14:30:00Z","datacontenttype":"application/json","data":{"id":"tx_abc123","status":"APPROVED"}}
```

O `X-Caf-Signature` o valor é calculado a partir desses bytes exatos e do segredo do seu webhook.

#### Com espaços, sem quebras de linha

```json
{ "id":"evt_123456789", "source":"TRANSACTION", "specversion":"1.0", "type":"TRANSACTIONSTATUSUPDATEDEVENT", "time":"2025-07-08T14:30:00Z", "datacontenttype":"application/json", "data":{"id":"tx_abc123","status":"APPROVED"} }
```

Embora esse JSON represente o mesmo objeto, sua assinatura é diferente porque seus bytes brutos são diferentes.

#### Com espaços e quebras de linha

```json
{
    "id": "evt_123456789",
    "source": "TRANSACTION",
    "specversion": "1.0",
    "type": "TRANSACTIONSTATUSUPDATEDEVENT",
    "time": "2025-07-08T14:30:00Z",
    "datacontenttype": "application/json",
    "data": {
        "id": "tx_abc123",
        "status": "APPROVED"
    }
}
```

Esse corpo formatado também produz uma assinatura diferente.

#### Com propriedades em ordem diferente

```json
{"time":"2025-07-08T14:30:00Z","type":"TRANSACTIONSTATUSUPDATEDEVENT","source":"TRANSACTION","specversion":"1.0","datacontenttype":"application/json","data":{"status":"APPROVED","id":"tx_abc123"},"id":"evt_123456789"}
```

Alterar a ordem das propriedades também altera a assinatura.

### Exemplos de código

#### Node.js

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, sigHeader, secret) {
  const signature = sigHeader;
  
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}
```

#### Java

```java
private String bytesToHexString(byte[] bytes) {
    var sb = new StringBuilder();
    for (var b : bytes) {
        var hex = String.format("%02x", b);
        sb.append(hex);
    }
    return sb.toString();
}

private boolean verifyHmacSHA256(String secret, String data, String expectedSignature) {
    try {
        var mac = Mac.getInstance("HmacSHA256");
        var secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKeySpec);
        var hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return bytesToHexString(hmacBytes).equals(expectedSignature);
    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
        return false;
    }
}
```

#### Go

```go
http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
  body, err := io.ReadAll(r.Body)
  if err != nil {
    fmt.Printf("não foi possível ler o corpo")
    w.WriteHeader(400)
    return
  }
  
  headerSignature := r.Header.Get("X-Caf-Signature")
  signature, err := hex.DecodeString(headerSignature)
  if err != nil {
    fmt.Printf("formato de assinatura inválido")
    w.WriteHeader(401)
    return
  }
  
  hasher := hmac.New(sha256.New, []byte(SECRET))
  hasher.Write(body)
  expected := hasher.Sum(nil)
  
  if !hmac.Equal(expected, signature) {
    fmt.Printf("assinatura inválida")
    w.WriteHeader(401)
    return
  }
  
  // Mensagem validada, processe o webhook
})
```

## Melhores práticas de segurança

{% hint style="danger" %}

1. **Sempre verifique as assinaturas** - Nunca confie em solicitações de webhook sem verificar suas assinaturas
2. **Processe os bytes brutos do corpo** - Use os bytes brutos do corpo para a verificação da assinatura, não o JSON analisado
3. **Use comparação em tempo constante** - Para evitar ataques de temporização, use uma função de comparação de strings em tempo constante
4. **Mantenha seu segredo de webhook seguro** - Nunca exponha seu segredo de webhook em código do lado do cliente
5. **Implemente idempotência** - Processe cada evento de webhook apenas uma vez, mesmo que seja recebido várias vezes
   {% endhint %}

## Obtendo seu segredo de webhook

Você pode encontrar seu segredo de webhook no Trust, a plataforma que gerencia as configurações, nas definições de configuração do webhook. Se você acredita que seu segredo foi comprometido, você pode gerar um novo a qualquer momento.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.caf.io/caf-api/caf-api-pt-br/connect/webhook/signature.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
