# QI Tech — Lending-as-a-Service › Primeiros Passos

Documentação da QI Tech em texto corrido, para colar em um LLM.
Fonte: https://docs.qitech.com.br
9 página(s).

Índice:
- Configurando Webhooks (/documentation/primeiros_passos/configurando_webhooks)
- Configurar IP de Integração (/documentation/primeiros_passos/configurar_ip_de_integracao)
- Introdução (/documentation/primeiros_passos/inicio)
- Endpoints de teste (/documentation/primeiros_passos/teste_de_autenticacao/endpoints_de_teste)
- Possíveis erros (/documentation/primeiros_passos/teste_de_autenticacao/possiveis_erros)
- Exemplo completo de teste de autenticação (/documentation/primeiros_passos/teste_de_autenticacao/teste_de_autenticacao_completo)
- Teste de autenticação (/documentation/primeiros_passos/teste_de_autenticacao/teste_de_autenticacao_v2)
- Validação de Webhooks (/documentation/primeiros_passos/teste_de_autenticacao/webhook_v2)
- Troca de chaves (/documentation/primeiros_passos/troca_de_chaves)

---

# Configurando Webhooks

URL: /documentation/primeiros_passos/configurando_webhooks

:::info Veja também
- [Validação de Webhooks](/documentation/primeiros_passos/teste_de_autenticacao/webhook_v2)
:::

Para configurar a URL de recepção das notificações faça login na plataforma QI Tech. Clique em "**Meu perfil**", localizado no menu lateral esquerdo, depois entre na aba Integração. Após isso, insira sua URL na divisão de "**Configurações de webhook**" da página e clique no botão “**SALVAR**”. Caso seja necessário configurar headers para as notificações enviadas é possível utilizar o campo seguinte conforme a imagem abaixo.

:::danger Atenção!
Os webhooks da QI Tech não devem ser mapeados de forma restrita. 
Campos adicionais podem ser incluídos aos payloads dos webhooks retornados em nossas APIs.
:::

:::info Reenvio de Webhooks
Você pode consultar e reenviar webhooks seguindo as instruções detalhadas na documentação: [Reenvio de Webhooks](/documentation/notificacoes/reenvio_de_notificacoes).
:::

:::info Informação
O timeout para resposta dos nossos webhooks é de 10 segundos.
:::

---

# Configurar IP de Integração

URL: /documentation/primeiros_passos/configurar_ip_de_integracao

:::info Veja também
- [Configurando Webhooks](/documentation/primeiros_passos/configurando_webhooks)
- [Validação de Webhooks](/documentation/primeiros_passos/teste_de_autenticacao/webhook_v2)
:::

A QI Tech permite que você restrinja as chamadas à sua integração a uma lista de **endereços IP públicos** (ou ranges CIDR) previamente autorizados. Esse filtro é aplicado antes da validação de assinatura: requisições originadas de IPs fora da sua lista ativa serão recusadas com erro `403 Forbidden` (`GDF000029`).

## Como configurar

Faça login na plataforma QI Tech, clique em "**Meu perfil**" no menu lateral esquerdo e entre na aba **Integração**. Role até a seção "**Whitelist de IPs da API**" e adicione um IP por vez no campo "**IP / CIDR**", clicando em "**ADICIONAR IP**" a cada inclusão.

Formatos aceitos:

- Endereço IPv4 público (ex.: `189.10.20.30`)
- Range CIDR IPv4 com prefixo `/24` ou maior (ex.: `200.100.50.0/24`)
- Endereço IPv6 público
- Range CIDR IPv6 com prefixo `/48` ou maior

Endereços privados/reservados (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16`) não são aceitos.

## Período de ativação (48 horas)

:::warning Atenção!
Por motivos de segurança, **todo IP cadastrado pelo painel fica em status `Pendente` por 48 horas** antes de se tornar ativo automaticamente. Durante esse período o IP é registrado mas **não autoriza requisições** — quem está na sua whitelist ativa atual continua valendo.

Você receberá um e-mail de confirmação no cadastro do IP e outro quando ele passar para o status `Ativo`.
:::

Após as 48 horas, o IP migra automaticamente para `Ativo` e passa a ser autorizado a chamar a API.

## Ativação emergencial

:::info Liberação imediata
Se você precisar liberar um IP **antes das 48 horas** (ex.: migração de servidor não planejada, incidente de produção), entre em contato com nosso suporte por meio dos canais oficiais e solicite a ativação manual informando:

- O `IP / CIDR` cadastrado
- A `client_integration_key` da sua integração
- O motivo da urgência

Nossa equipe de operações fará a promoção manual e o IP entrará em vigor imediatamente.
:::

## Erros comuns

| Código | Causa | Solução |
|---|---|---|
| `403` / `GDF000029` | Requisição partiu de um IP que não está em status `Ativo` na sua whitelist | Verifique na tela de Integração quais IPs estão ativos. Se o IP recém-cadastrado ainda está `Pendente`, aguarde as 48h ou solicite ativação manual. |
| Cadastro de IP rejeitado | IP/CIDR inválido, privado, ou range muito grande (prefixo menor que `/24` IPv4 ou `/48` IPv6) | Use somente endereços públicos e respeite os tamanhos mínimos de prefixo. |

---

# Introdução

URL: /documentation/primeiros_passos/inicio

Somos a primeira instituição financeira a criar um modelo exclusivo de Bank-as-a-Service (BaaS) do Brasil. Nosso objetivo é ajudar qualquer Fintech/Gestora de Crédito ou empresa a ter acesso a serviços financeiros rápidos, ágeis e seguros, da maneira que quiser. Saiba mais em https://qitech.com.br.

Essa documentação tem como objetivo descrever os diversos endpoints das nossas APIs.

Obs.: Em caso de dúvidas em qualquer etapa do processo, favor entrar em contato com [api@qitech.com.br](mailto:api@qitech.com.br) detalhando seu problema/dúvida que te auxiliaremos.

## Primeiros passos

Antes de iniciar as operações enviando requisições via API para consumo dos serviços QI Tech, é importante que um operador representante da empresa originadora realize os seguintes passos **em ambiente de sandbox**.

## Criação de Perfil de Acesso

1. Enviar solicitação de criação de acesso para o e-mail api@qitech.com.br informando os seguintes dados:
   1. CNPJ da empresa
   2. Nome completo do usuário Master
   3. CPF do usuário Master
   4. E-mail do usuário Master
   5. Telefone celular do usuário Master
2. Após a criação do acesso pelo time da QI Tech, o usuário Master receberá um e-mail com um link para acesso a plataforma da QI Tech em sandbox e uma senha provisória.
3. Ao realizar o primeiro acesso à plataforma o usuário Master deve redefinir a senha de acesso.

## Iniciando a jornada de integração

Existem várias combinações de endpoints que podem ser utilizadas de acordo com a necessidade do parceiro, porém os três primeiros passos são universais independente dos serviços utilizados:

Passo 1: Realizar a validação de Token através do portal QI Tech
Passo 2: [Realizar a troca de chaves e gerar credenciais de integração](/documentation/primeiros_passos/troca_de_chaves)
Passo 3: [Realizar o teste de autenticação](/documentation/primeiros_passos/teste_de_autenticacao/teste_de_autenticacao_v2)
Passo 4: [Configurar a URL de recebimento de webhooks](/documentation/primeiros_passos/configurando_webhooks)

## Informações importantes

Para utilizar nossa API em produção é necessário que se entre com contato com [comercial@qitech.com.br](mailto:comercial@qitech.com.br) para contato comercial e configuração da integração.

---

# Endpoints de teste

URL: /documentation/primeiros_passos/teste_de_autenticacao/endpoints_de_teste

:::info Veja também
- [Teste de autenticação](./teste_de_autenticacao_v2)
- [Exemplo completo de autenticação](./teste_de_autenticacao_completo)
- [Possíveis erros](./possiveis_erros)
:::

## Método GET

### Request

ENDPOINT /test/ API_KEY
MÉTODO GET

### Path Params

| Campo | Tipo | Descrição |
|-|-|-|
| `api_key` * | string | API_KEY do parceiro. |

<CodeSample endpoint="GET /test/{api_key}" samples={{
  curl: `curl -X GET \\
  'https://api-auth.sandbox.qitech.app/test/{api_key}' \\
  -H 'AUTHORIZATION: {encoded_header_token}' \\
  -H 'API-CLIENT-KEY: {api_key}'`,
  python: `import requests

url = f"{base_url}/test/{api_key}"
response = requests.get(url=url, headers=signed_header)
print(response.json())`,
  javascript: `const url = \`\${base_url}/test/\${api_key}\`;
const response = await fetch(url, {
  method: 'GET',
  headers: {
    'AUTHORIZATION': encoded_header_token,
    'API-CLIENT-KEY': api_key,
  },
});
const data = await response.json();
console.log(data);`,
  java: `OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url(requestUrl + "/" + api_key)
    .headers(Headers.of(headers))
    .get()
    .build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());`,
}} />

### Response

STATUS 200

Response Body

```json
{
  "test_key": "97ad0301-869c-4481-98b6-294b139e09ae",
  "success": "Congrats!"
}
```

## Método POST

### Request

ENDPOINT test/ API_KEY
MÉTODO POST

<CodeSample endpoint="POST /test/{api_key}" samples={{
  curl: `curl -X POST \\
  'https://api-auth.sandbox.qitech.app/test/{api_key}' \\
  -H 'AUTHORIZATION: {encoded_header_token}' \\
  -H 'API-CLIENT-KEY: {api_key}' \\
  -H 'Content-Type: application/json' \\
  -d '{"name": "QI Tech"}'`,
  python: `import requests

url = f"{base_url}/test/{api_key}"
body = {"name": "QI Tech"}
response = requests.post(url=url, headers=signed_header, json=body)
print(response.json())`,
  javascript: `const url = \`\${base_url}/test/\${api_key}\`;
const response = await fetch(url, {
  method: 'POST',
  headers: {
    'AUTHORIZATION': encoded_header_token,
    'API-CLIENT-KEY': api_key,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: 'QI Tech' }),
});
const data = await response.json();
console.log(data);`,
  java: `OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(
    mediaType, "{\\"name\\": \\"QI Tech\\"}");
Request request = new Request.Builder()
    .url(requestUrl + "/" + api_key)
    .headers(Headers.of(headers))
    .post(body)
    .build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());`,
}} />

Request Body

```json
{
  "name": "QI Tech"
}
```

### Path Params

| Campo | Tipo | Descrição |
|-|-|-|
| `api_key` * | string | API_KEY do parceiro. |

### Response

STATUS 201

Response Body

```json
{
  "name": "QI Tech",
  "success": "Congrats!"
}
```

---

# Possíveis erros

URL: /documentation/primeiros_passos/teste_de_autenticacao/possiveis_erros

:::info Veja também
- [Teste de autenticação](./teste_de_autenticacao_v2)
- [Exemplo completo de autenticação](./teste_de_autenticacao_completo)
- [Endpoints de teste](./endpoints_de_teste)
:::

## Erro no token

Caso a assinatura da string_to_sign esteja incorreta, um erro será apresentado relativo ao encoded_header_token :

STATUS 401

Response Body

```json
{
	"title": "QI Unauthenticated",
	"description": "Please provide valid credentials as part of the request. (Documentation: https://qitech.com.br/documentation) Details: Failed while decoding the authentication token",
	"translation": "Por favor forneça credenciais válidas como parte da request. (Documentação: https://qitech.com.br/documentation) Detalhes: Falha ao decodificar o token de autenticação",
	"code": "GDF000014"
}
```

## Erro no <strong>API_KEY</strong>

Caso a API_KEY não seja enviada no header o seguinte erro será apresentado:

STATUS 400

Response Body

```json
{
	"title": "Bad Request",
	"description": "No API Client Key received",
	"translation": "Nenhuma chave de API do cliente recebida",
	"code": "GDF000003"
}
```

## <strong>API_KEY</strong> incorreta

Caso a API_KEY enviada não corresponda a API_KEY apresentada no front QI Tech após o cadastro de chaves o seguinte retorno será apresentado:

STATUS 404

Response Body

```json
  {
  	"code": "GDF000018",
  	"title": "Not Found",
  	"description": "No ClientIntegration found for api_client_key: {api_client_key}.",
  	"translation": "Nenhuma ClientIntegration encontrada para api_client_key: {api_client_key}."
  }
```

## Endpoint não autorizado

Caso o endpoint ou método acessado não esteja autorizado o seguinte erro será retornado:

STATUS 401

Response Body

```json
{
	"title": "QI Unauthenticated",
	"description": "Please provide valid credentials as part of the request. (Documentation: https://qitech.com.br/documentation) Details: Endpoint or HTTP method not allowed for the given ClientIntegration (Action: POST /debt)",
	"translation": "Por favor forneça credenciais válidas como parte da request. (Documentação: https://qitech.com.br/documentation) Detalhes: Endpoint ou método HTTP não permitido para a ClientIntegration fornecida (Action: POST /debt)",
	"code": "GDF000014"
}
```

:::caution Atenção!

Para requisitar acesso ao endpoint que retornou o erro referido, é necessário solicitar a liberação ao time de suporte QI Tech.
:::

---

# Exemplo completo de teste de autenticação

URL: /documentation/primeiros_passos/teste_de_autenticacao/teste_de_autenticacao_completo

:::info Veja também
- [Teste de autenticação passo a passo](./teste_de_autenticacao_v2)
- [Endpoints de teste](./endpoints_de_teste)
- [Possíveis erros](./possiveis_erros)
:::

### Visão Geral
Esta documentação detalha o processo de assinatura e encriptação de cabeçalhos para autenticação segura em requisições à nossa API. O processo garante que as requisições sejam confiáveis e seguras, prevenindo acessos não autorizados e garantindo a integridade dos dados.

:::caution Atenção!

O hash md5 de requisições dos métodos GET e DELETE deve ser gerado com o payload vazio
:::

**Python**

```python
#Aqui, importamos as bibliotecas necessárias ao longo do processo de autenticação.
from jose import jwt
import json
from datetime import datetime
from hashlib import md5
import requests

def get_auth_header(endpoint, method, CLIENT_PRIVATE_KEY, API_KEY, request_body=None):

    if request_body is None:
        request_body = {}

    #O objeto de data e hora informado deve estar em UTC e deve seguir o padrão da norma internacional ISO 8601 ("2023-06-26T19:48:32.759844Z")
    timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ")

    #Definimos o algoritmo de codificação JWT
    jwt_header = {
        "typ": "JWT",
        "alg": "ES512"
    }

    #Construir hash em MD5 para assinatura no cabeçalho (header) utilizando o payload
    json_body = json.dumps(request_body)
    md5_hash = md5(json_body.encode()).hexdigest()

    #Essas são as infromações necessárias para assinatura do cabeçalho
    jwt_body = {
        "payload_md5": md5_hash,
        "timestamp": timestamp,
        "method": method,
        "uri": endpoint
    }

    #Realizar criptografia do header
    encoded_header_token = jwt.encode(
        claims=jwt_body,
        key=CLIENT_PRIVATE_KEY,
        algorithm="ES512",
        headers=jwt_header
    )

    #Montar header assinado
    signed_header = {
        "AUTHORIZATION": encoded_header_token,
        "API-CLIENT-KEY": API_KEY
    }

    return signed_header

if __name__ == "__main__":

    #Utilizaremos as variáveis base_url, endpoint, method e request_body. Neste exemplo faremos um POST no endpoint "/test".
    #As chaves contidas neste exemplo são apenas para fins de demonstração. Por favor, utilize suas próprias chaves.
    CLIENT_PRIVATE_KEY = "SUA PRIVATE KEY AQUI"
    API_KEY = "SUA API KEY AQUI"

    BASE_URL = "https://api-auth.sandbox.qitech.app"
    METHOD = "POST" #GET ou POST
    REQUEST_BODY = {
        "name": "QI Tech"
    }

    #Para fazer um GET na /test, é necessário inserir a API key ao final, enquanto para fazer um POST no mesmo endpoint, não é necessário
    if METHOD == 'GET':
        ENDPOINT = f"/test/{API_KEY}"
        signed_header = get_auth_header(ENDPOINT, METHOD, CLIENT_PRIVATE_KEY, API_KEY)
        response = requests.get(f"{BASE_URL}{ENDPOINT}", headers=signed_header)
    else:
        ENDPOINT = f"/test/"
        signed_header = get_auth_header(ENDPOINT, METHOD, CLIENT_PRIVATE_KEY, API_KEY, REQUEST_BODY)
        response = requests.post(f"{BASE_URL}{ENDPOINT}", json=REQUEST_BODY, headers=signed_header)

    print(response.status_code)
    print(response.json())
```

**PHP**

```php
<?php
require __DIR__ . '/vendor/autoload.php';

//Aqui, importamos as bibliotecas necessárias ao longo do processo de autenticação.
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Signature\JWSTokenSupport;
use Jose\Component\Signature\Algorithm\ES512;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\JWSBuilder;

function get_auth_header($endpoint, $method, $privateKeyString, $api_key, $request_body = null) {

    if ($request_body === null) {
        $request_body = (object)[];
    }

    //O objeto de data e hora informado deve estar em UTC e deve seguir o padrão da norma internacional ISO 8601 ("2023-06-26T19:48:32.759844Z")
    $microtime_float = microtime(true);
    $datetime = new DateTimeImmutable('@' . floor($microtime_float), new DateTimeZone('UTC'));
    $timestamp = $datetime->format('Y-m-d\TH:i:s.') . sprintf('%06d', ($microtime_float - floor($microtime_float)) * 1000000) . 'Z';

    //Definimos o algoritmo de codificação JWT
    $header = [
        "typ" => "JWT",
        "alg" => "ES512"
    ];

    //Construir hash em MD5 para assinatura no cabeçalho (header) utilizando o payload
    $request_body_json = json_encode($request_body);
    $md5_hash = md5($request_body_json);

    //Essas são as infromações necessárias para assinatura do cabeçalho
    $payload = [
        "payload_md5" => $md5_hash,
        "timestamp" => $timestamp,
        "method" => $method,
        "uri" => $endpoint
    ];

    // Inicializar Algorithm Manager com ES512
    $algorithmManager = new AlgorithmManager([
        new ES512(),
    ]);

    // Inicializar JWS Builder
    $jwsBuilder = new JWSBuilder(
        $algorithmManager,
        new JWSTokenSupport()
    );

    $privateKey = JWKFactory::createFromKey($privateKeyString);

    //Realizar criptografia do header
    $jws = $jwsBuilder
        ->create()
        ->withPayload(json_encode($payload))
        ->addSignature($privateKey, $header)
        ->build();

    $serializer = new CompactSerializer();
    $jwt = $serializer->serialize($jws, 0);

    //Montar header assinado
    $headers = [
        'Authorization' => $jwt,
        'API-CLIENT-KEY' => $api_key,
    ];

    return $headers;
}

if (php_sapi_name() == 'cli' || (isset($_SERVER['REQUEST_METHOD']) && realpath($_SERVER['SCRIPT_FILENAME']) === __FILE__)) {
    
    //Utilizaremos as variáveis base_url, endpoint, method e request_body. Neste exemplo faremos um POST no endpoint "/test".
    $base_url = "https://api-auth.sandbox.qitech.app";
    $method = "POST"; // HTTP method: "GET" or "POST"

    $request_body = ["name" => "QI Tech"];

    //As chaves contidas neste exemplo são apenas para fins de demonstração. Por favor, utilize suas próprias chaves.
    $api_key = "SUA API KEY AQUI";
    $privateKeyString = "SUA PRIVATE KEY AQUI";

    $response = null;

    #Para fazer um GET na /test, é necessário inserir a API key ao final, enquanto para fazer um POST no mesmo endpoint, não é necessário
    if ($method == 'GET') {
        $endpoint = "/test/" . $api_key;
        $headers = get_auth_header($endpoint, $method, $privateKeyString, $api_key);
        $url = $base_url . $endpoint;
        $response = \WpOrg\Requests\Requests::get($url, $headers);
    } else {
        $endpoint = "/test";
        $headers = get_auth_header($endpoint, $method, $privateKeyString, $api_key, $request_body);
        $url = $base_url . $endpoint;
        $response = \WpOrg\Requests\Requests::post($url, $headers, json_encode($request_body));
    }

    if ($response) {
        echo "HTTP Status Code: " . $response->status_code . "\n";

        $json_response = json_decode($response->body, true);
        if (json_last_error() === JSON_ERROR_NONE) {
            echo "Response JSON:\n";
            print_r($json_response);
        } else {
            echo "Error decoding JSON. Raw Response Text:\n";
            echo $response->body . "\n";

    }
}
?>
```

**Node.js**

```js
//Aqui, importamos as bibliotecas necessárias ao longo do processo de autenticação.
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const axios = require('axios');

function getAuthHeader(endpoint, method, client_private_key, api_key, request_body = null) {
    if (request_body === null) {
        request_body = {};
    }

    //O objeto de data e hora informado deve estar em UTC e deve seguir o padrão da norma internacional ISO 8601 ("2023-06-26T19:48:32.759844Z")
    const now = new Date();
    const isoString = now.toISOString();
    const timestamp = isoString.slice(0, -1) + (now.getMilliseconds() * 1000).toString().padStart(6, '0').slice(0, 3) + 'Z';

    //Definimos o algoritmo de codificação JWT
    const jwt_header = {
        typ: 'JWT',
        alg: 'ES512'
    };

    //Construir hash em MD5 para assinatura no cabeçalho (header) utilizando o payload
    const str_body = JSON.stringify(request_body);
    const md5_hash = crypto.createHash('md5').update(str_body).digest('hex');

    //Essas são as infromações necessárias para assinatura do cabeçalho
    const jwt_body = {
        payload_md5: md5_hash,
        timestamp: timestamp,
        method: method,
        uri: endpoint
    };

    // Inicializar JWS Builder
    const encoded_header_token = jwt.sign(
        jwt_body,
        client_private_key,
        {
            algorithm: 'ES512',
            header: jwt_header
        }
    );

    //Realizar criptografia do header
    const signed_header = {
        'AUTHORIZATION': encoded_header_token,
        'API-CLIENT-KEY': api_key
    };

    return signed_header;
}
//Utilizaremos as variáveis BASE_URL, ENDPOINT, METHOD e REQUEST_BODY. Neste exemplo faremos um POST no endpoint "/test".
async function main() {
    const BASE_URL = "https://api-auth.sandbox.qitech.app";
    const METHOD = "POST"; //"POST" ou "GET"

    const REQUEST_BODY = {
        name: "QI Tech"
    };

    const API_KEY = "SUA API KEY AQUI";
    const CLIENT_PRIVATE_KEY = "SUA PRIVATE KEY AQUI";

    let ENDPOINT;
    let url;
    let signed_header;

    try {
    //Para fazer um GET na /test, é necessário inserir a API key ao final, enquanto para fazer um POST no mesmo endpoint, não é necessário
        if (METHOD === 'GET') {
            ENDPOINT = `/test/${API_KEY}`;
            url = `${BASE_URL}${ENDPOINT}`;
            signed_header = getAuthHeader(ENDPOINT, METHOD, CLIENT_PRIVATE_KEY, API_KEY);

            const response = await axios.get(url, { headers: signed_header });
            console.log("Status Code:", response.status);
            console.log("Response Body:", response.data);

        } else {
            ENDPOINT = "/test";
            url = `${BASE_URL}${ENDPOINT}`; // Corrected string interpolation
            signed_header = getAuthHeader(ENDPOINT, METHOD, CLIENT_PRIVATE_KEY, API_KEY, REQUEST_BODY);

            const response = await axios.post(url, REQUEST_BODY, { headers: signed_header });
            console.log("Status Code:", response.status);
            console.log("Response Body:", response.data);
        }
    } catch (error) {
        // More robust error handling for Axios
        if (error.response) {
            console.error("API Error - Status Code:", error.response.status);
            console.error("API Error - Response Data:", error.response.data);
            console.error("API Error - Headers:", error.response.headers);
        } else if (error.request) {
            console.error("Network Error: No response received from server.");
            console.error("Request:", error.request);
        } else {
            console.error("Error setting up request:", error.message);
        }
        console.error("Full Error Object:", error);
    }
}

main();
```

**Java**

```java
//Para Java precisaremos criar um arquivo com o nome de qitech-java-client
//Crie um arquivo chamado pom.xml e cole o final do código dentro dele
// Será necessário dentro do seu projeto criar algumas pastas - crie o seguinte path; src > main > java > com > qitech > api e insira seu arquivo java dentro com o nome de QItechApiClient.java
// Adicione sua private key no diretório raiz de seu projeto no mesmo nível que seu pom
// Para rodar o código abra o terminal ou comand prompt, navegue para a raiz de seu projeto e rode o seguinte código:
// mvn clean install exec:java

//Aqui, importamos as bibliotecas necessárias ao longo do processo de autenticação.
package com.qitech.api;

import com.google.gson.Gson;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;

public class QItechApiClient {

    public static Map<String, String> getAuthHeader(String endpoint, String method, PrivateKey privateKey, String apiKey, Map<String, Object> requestBody) throws NoSuchAlgorithmException {        
        //O objeto de data e hora informado deve estar em UTC e deve seguir o padrão da norma internacional ISO 8601 ("2023-06-26T19:48:32.759844Z")
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'");
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        String timestamp = sdf.format(new Date());

        //Construir hash em MD5 para assinatura no cabeçalho (header) utilizando o payload
        String jsonBody = jsonToString(requestBody);
        String md5Hash = md5Hash(jsonBody);

        //Essas são as infromações necessárias para assinatura do cabeçalho
        Map<String, Object> jwtBody = new HashMap<>();
        jwtBody.put("payload_md5", md5Hash);
        jwtBody.put("timestamp", timestamp);
        jwtBody.put("method", method);
        jwtBody.put("uri", endpoint);

        //Realizar criptografia do header
        JwtBuilder jwtBuilder = Jwts.builder()
                .setClaims(jwtBody)
                .signWith(privateKey, SignatureAlgorithm.ES512);
        String encodedHeaderToken = jwtBuilder.compact();

        //Montar header assinado
        Map<String, String> signedHeader = new HashMap<>();
        signedHeader.put("AUTHORIZATION", encodedHeaderToken);
        signedHeader.put("API-CLIENT-KEY", apiKey);

        return signedHeader;
    }

    public static void main(String[] args) {
        Security.addProvider(new BouncyCastleProvider());
        OkHttpClient client = null;

        try {

            //Utilizaremos as variáveis base_url, endpoint, method e request_body. Neste exemplo faremos um POST no endpoint "/test".
                //As chaves contidas neste exemplo são apenas para fins de demonstração. Por favor, utilize suas próprias chaves.
            final String BASE_URL = "https://api-auth.sandbox.qitech.app";
            final String PRIVATE_KEY_FILENAME = "private.key";

            final String API_CLIENT_KEY = "SUA API KEY AQUI";
            
            final String METHOD = "GET";  //GET ou POST
            final Map<String, Object> REQUEST_BODY = new HashMap<>();
            REQUEST_BODY.put("name", "QI Tech");

            
            String keyFromFile = readKeyFromFile(PRIVATE_KEY_FILENAME);
            PrivateKey privateKey = getPrivateKey(keyFromFile);

            String endpoint;
            Request request;

            //Para fazer um GET na /test, é necessário inserir a API key ao final, enquanto para fazer um POST no mesmo endpoint, não é necessário

            if ("GET".equalsIgnoreCase(METHOD)) {
                endpoint = "/test/" + API_CLIENT_KEY;

                Map<String, String> headers = getAuthHeader(endpoint, "GET", privateKey, API_CLIENT_KEY, Collections.emptyMap());

                request = new Request.Builder()
                        .url(BASE_URL + endpoint)
                        .headers(okhttp3.Headers.of(headers))
                        .get()
                        .build();

            } else {
                endpoint = "/test";

                Map<String, String> headers = getAuthHeader(endpoint, "POST", privateKey, API_CLIENT_KEY, REQUEST_BODY);

                RequestBody body = RequestBody.create(
                    jsonToString(REQUEST_BODY),
                    MediaType.parse("application/json; charset=utf-8")
                );

                request = new Request.Builder()
                        .url(BASE_URL + endpoint)
                        .headers(okhttp3.Headers.of(headers))
                        .post(body)
                        .build();
            }

            client = new OkHttpClient.Builder()
                    .connectTimeout(30, TimeUnit.SECONDS)
                    .readTimeout(30, TimeUnit.SECONDS)
                    .build();

            System.out.println("--- Sending " + METHOD + " Request ---");
            System.out.println("URL: " + BASE_URL + endpoint);

            try (Response response = client.newCall(request).execute()) {
                System.out.println("\n--- Received Response ---");
                System.out.println("Status Code: " + response.code());
                if (response.body() != null) {
                    System.out.println("Response Body: " + response.body().string());
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (client != null) {
                client.dispatcher().executorService().shutdown();
                client.connectionPool().evictAll();
            }
        }
    }

    // --- Helper Methods ---
    
    private static String readKeyFromFile(String filename) throws IOException {
        String key = new String(Files.readAllBytes(Paths.get(filename)));
        return key.replace("-----BEGIN EC PRIVATE KEY-----", "")
                  .replace("-----END EC PRIVATE KEY-----", "")
                  .replace("-----BEGIN PRIVATE KEY-----", "")
                  .replace("-----END PRIVATE KEY-----", "")
                  .replaceAll("\\s", "");
    }

    private static String jsonToString(Map<String, Object> jsonMap) { return new Gson().toJson(jsonMap); }

    private static String md5Hash(String text) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(text.getBytes());
        StringBuilder sb = new StringBuilder();
        for (byte b : array) { sb.append(String.format("%02x", b)); }
        return sb.toString();
    }

    private static PrivateKey getPrivateKey(final String encodedPvKey) throws IOException {
        try {
            byte[] derBytes = Base64.getDecoder().decode(encodedPvKey);
            KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            try {
                return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(derBytes));
            } catch (InvalidKeySpecException e) {
                org.bouncycastle.asn1.sec.ECPrivateKey sec1Key = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(derBytes);
                ECParameterSpec ecParameterSpec = ECNamedCurveTable.getParameterSpec("secp521r1");
                ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(sec1Key.getKey(), ecParameterSpec);
                return keyFactory.generatePrivate(privateKeySpec);
            }
        } catch (Exception e) {
            throw new IOException("Failed to parse private key. Key is corrupted or not a valid EC key.", e);
        }
    }
}

////POM File

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qitech.api</groupId>
    <artifactId>qitech-api-client</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- HTTP Client -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.12.0</version>
        </dependency>

        <!-- JSON Web Token (JWT) Handling -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.12.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.12.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.12.5</version>
            <scope>runtime</scope>
        </dependency>

        <!-- Cryptography Provider for ES512 -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk18on</artifactId>
            <version>1.78</version>
        </dependency>

        <!-- JSON Serialization -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <mainClass>com.qitech.api.QItechApiClient</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

```

**C#**

```c#
//Aqui, importamos as bibliotecas necessárias ao longo do processo de autenticação.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Jose;
using Newtonsoft.Json;

public static class QiTechAuthGenerator {
    public static string GetAuthorizationHeader(
        string endpoint,
        string method,
        string clientPrivateKey,
        object requestBody)
    {
        string privateKeyBase64 = clientPrivateKey 
            .Replace("-----BEGIN EC PRIVATE KEY-----", "")
            .Replace("-----END EC PRIVATE KEY-----", "")
            .Replace("\n", "")
            .Replace("\r", "");

        using var privateKey = ECDsa.Create();
        privateKey.ImportECPrivateKey(Convert.FromBase64String(privateKeyBase64), out _);

        string payloadToHash;

        if (method.ToUpper() == "GET") {
            payloadToHash = "{}";
        }
        else {
            payloadToHash = JsonConvert.SerializeObject(requestBody);
        }
        
        //Construir hash em MD5 para assinatura no cabeçalho (header) utilizando o payload
        var payloadMd5Hash = CalculateMd5Hash(payloadToHash);

        //Essas são as infromações necessárias para assinatura do cabeçalho
        var jwtBody = new Dictionary<string, object> {
            { "payload_md5", payloadMd5Hash },
            { "timestamp", timestamp },
            { "method", method },
            { "uri", endpoint }
        };

        //Definimos o algoritmo de codificação JWT
        var jwtHeader = new Dictionary<string, object> {
            { "typ", "JWT" },
            { "alg", "ES512" }
        };

        return JWT.Encode(jwtBody, privateKey, JwsAlgorithm.ES512, jwtHeader);
    }

    //Construir hash em MD5 para assinatura no cabeçalho (header) utilizando o payload
    private static string CalculateMd5Hash(string input) {
        using (var md5 = MD5.Create()) {
            byte[] inputBytes = Encoding.UTF8.GetBytes(input);
            byte[] hashBytes = md5.ComputeHash(inputBytes);

            var builder = new StringBuilder();
            foreach (var b in hashBytes) {
                builder.Append(b.ToString("x2"));
            }
            return builder.ToString();
        }
    }
}

public class QiTechApiClient {
    private readonly string _baseUrl;
    private readonly string _apiKey;
    private readonly string _clientPrivateKey;

    public QiTechApiClient(string baseUrl, string apiKey, string clientPrivateKey) {
        _baseUrl = baseUrl;
        _apiKey = apiKey;
        _clientPrivateKey = clientPrivateKey;
    }

    //Realizar criptografia do header
    public async Task<string> CallEndpointAsync(string endpoint, string method, object requestBody) {
        var signedHeader = QiTechAuthGenerator.GetAuthorizationHeader(
            endpoint,
            method,
            _clientPrivateKey,
            requestBody
        );

        var url = $"{_baseUrl}{endpoint}";

        //Montar header assinado
        using (var client = new HttpClient()) {
            client.DefaultRequestHeaders.Add("AUTHORIZATION", signedHeader);
            client.DefaultRequestHeaders.Add("API-CLIENT-KEY", _apiKey);

            HttpResponseMessage httpResponse;

            if (method.ToUpper() == "GET") {
                httpResponse = await client.GetAsync(url);
            }
            else {
                var jsonBody = JsonConvert.SerializeObject(requestBody);
                var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
                httpResponse = await client.PostAsync(url, content);
            }

            httpResponse.EnsureSuccessStatusCode();

            var responseContent = await httpResponse.Content.ReadAsStringAsync();

            return responseContent;
        }
    }
}

public class Program {
    public static async Task Main() {
        string response = "";
        
        //Utilizaremos as variáveis baseUrl, endpoint, method e requestBody. Neste exemplo faremos um POST no endpoint "/test".
        var baseUrl = "https://api-auth.sandbox.qitech.app";
        var method = "POST";
        var requestBody = new { name = "QI Tech" };
        
        //As chaves contidas neste exemplo são apenas para fins de demonstração. Por favor, utilize suas próprias chaves.
        var apiKey = "SUA API KEY AQUI";
        var clientPrivateKey = @"SUA PRIVATE KEY AQUI";

        try {
            var apiClient = new QiTechApiClient(baseUrl, apiKey, clientPrivateKey);

            //Para fazer um GET na /test, é necessário inserir a API key ao final, enquanto para fazer um POST no mesmo endpoint, não é necessário
            if (method.ToUpper() == "GET") {
                var endpoint = "/test/" + apiKey;
                response = await apiClient.CallEndpointAsync(endpoint, method, null);
            }
            else {
                var endpoint = "/test";
                response = await apiClient.CallEndpointAsync(endpoint, method, requestBody);
            }

            Console.WriteLine("\nAPI Response:");
            Console.WriteLine(response);
        }
        catch (HttpRequestException ex) {
            Console.WriteLine($"\nHTTP Error: {ex.Message}");
        }
        catch (Exception ex) {
            Console.WriteLine($"\nAn unexpected error occurred: {ex.Message}");
        }
    }
}
```

---

# Teste de autenticação

URL: /documentation/primeiros_passos/teste_de_autenticacao/teste_de_autenticacao_v2

:::info Veja também
- [Exemplo completo de autenticação](./teste_de_autenticacao_completo)
- [Endpoints de teste](./endpoints_de_teste)
- [Possíveis erros](./possiveis_erros)
:::

## 1. Introdução e Configuração Inicial

### Visão Geral
Esta documentação detalha o processo de assinatura e encriptação de cabeçalhos para autenticação segura em requisições à nossa API. O processo garante que as requisições sejam confiáveis e seguras, prevenindo acessos não autorizados e garantindo a integridade dos dados.

### Importar Bibliotecas
Aqui, importamos as bibliotecas necessárias ao longo do processo de autenticação.

**Python**

```python
import json
import requests
from datetime import datetime, timezone
from hashlib import md5
from jose import jwt
```

**PHP**

```php
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Signature\JWSTokenSupport;
use Jose\Component\Signature\Algorithm\ES512;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\KeyManagement\JWKFactory;
```

**Node.js**

```js
const jose = require('jose');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const axios = require('axios');
```

**Java**

```java
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

import java.io.IOException;
import java.io.StringReader;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
```

**C#**

```c#
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using Jose;
using Newtonsoft.Json;
```

### Definir variáveis

Utilizaremos as variáveis _base_url_, _endpoint_, _method_ e _request_body_. Neste exemplo faremos um POST no endpoint "/test".

**Python**

```python
base_url = "https://api-auth.sandbox.qitech.app"
endpoint = "/test"
method = "POST"
request_body = {"name": "QI Tech"}
```
  

**PHP**

```php
$base_url = "https://api-auth.sandbox.qitech.app";
$endpoint = "/test";
$method = "POST";
$request_body = ["name" => "QI Tech"];
```
  

**Node.js**

```js
const base_url = 'https://api-auth.sandbox.qitech.app';
const endpoint = '/test';
const method = 'POST';
const request_body = { name: 'QI Tech' };
```
  

**Java**

```java
private static final String base_url = "https://api-auth.sandbox.qitech.app";
private static final String endpoint = "/test";
private static final String method = "POST";
private static final Map<String, Object> request_body = new HashMap<>();
static {
    request_body.put("name", "QI Tech");
}
```
  

**C#**

```c#
var base_url = "https://api-auth.sandbox.qitech.app";
var endpoint = "/test";
var method = "POST";
var request_body = new { name = "QI Tech" };

```
  

## 2. Preparação de Dados para Assinatura

### Inserir dados de criptografia

As chaves contidas neste exemplo são apenas para fins de demonstração. Por favor, utilize suas próprias chaves. 

**Python**

```python
api_key = "f19c6e62-bd82-4334-9839-020810550c44" 

client_private_key = '''-----BEGIN EC PRIVATE KEY-----
MIHbAgEBBEHh1hIeOPE5XNNhn6bxRAmVswsPZ0wZCmzVvP8Tl/LZK9ofVmRVGzll
srU1uezJEyHKYdOHrE2p52xUj+pHzjJvb6AHBgUrgQQAI6GBiQOBhgAEAAofUz1J
hBSOyGHLsnV9Sz0DSWmhl7U+ljqbfa8PKVFWSV3w16I1v2zME5/UzUhHn1gWsjnv
7/ekcLLAQbvqMPNXAfjIhFXLAPzqbB9iCuVua1v0Vgy52rBemOWrJka/Ws2bnKR8
h1N1OxOYeYr6C2jqMygBLktKMAs+282CEiEb4bIv
-----END EC PRIVATE KEY-----'''  
```
  

**PHP**

```php
$api_key = "f19c6e62-bd82-4334-9839-020810550c44"; 

$privateKeyString = "-----BEGIN EC PRIVATE KEY-----
MIHbAgEBBEHh1hIeOPE5XNNhn6bxRAmVswsPZ0wZCmzVvP8Tl/LZK9ofVmRVGzll
srU1uezJEyHKYdOHrE2p52xUj+pHzjJvb6AHBgUrgQQAI6GBiQOBhgAEAAofUz1J
hBSOyGHLsnV9Sz0DSWmhl7U+ljqbfa8PKVFWSV3w16I1v2zME5/UzUhHn1gWsjnv
7/ekcLLAQbvqMPNXAfjIhFXLAPzqbB9iCuVua1v0Vgy52rBemOWrJka/Ws2bnKR8
h1N1OxOYeYr6C2jqMygBLktKMAs+282CEiEb4bIv
-----END EC PRIVATE KEY-----"; 
```
  

**Node.js**

```js
const api_key = 'f19c6e62-bd82-4334-9839-020810550c44'; 

const client_private_key = `-----BEGIN EC PRIVATE KEY-----
MIHbAgEBBEHh1hIeOPE5XNNhn6bxRAmVswsPZ0wZCmzVvP8Tl/LZK9ofVmRVGzll
srU1uezJEyHKYdOHrE2p52xUj+pHzjJvb6AHBgUrgQQAI6GBiQOBhgAEAAofUz1J
hBSOyGHLsnV9Sz0DSWmhl7U+ljqbfa8PKVFWSV3w16I1v2zME5/UzUhHn1gWsjnv
7/ekcLLAQbvqMPNXAfjIhFXLAPzqbB9iCuVua1v0Vgy52rBemOWrJka/Ws2bnKR8
h1N1OxOYeYr6C2jqMygBLktKMAs+282CEiEb4bIv
-----END EC PRIVATE KEY-----`; 
```
  

**Java**

```java
private static final String clientPrivateKey = "MIHbAgEBBEHh1hIeOPE5XNNhn6bxRAmVswsPZ0wZCmzVvP8Tl/LZK9ofVmRVGzll
srU1uezJEyHKYdOHrE2p52xUj+pHzjJvb6AHBgUrgQQAI6GBiQOBhgAEAAofUz1J
hBSOyGHLsnV9Sz0DSWmhl7U+ljqbfa8PKVFWSV3w16I1v2zME5/UzUhHn1gWsjnv
7/ekcLLAQbvqMPNXAfjIhFXLAPzqbB9iCuVua1v0Vgy52rBemOWrJka/Ws2bnKR8
h1N1OxOYeYr6C2jqMygBLktKMAs+282CEiEb4bIv"; 
```
  

**C#**

```c#
var api_key = "f19c6e62-bd82-4334-9839-020810550c44"; 
var client_private_key = @"-----BEGIN EC PRIVATE KEY-----
MIHbAgEBBEHh1hIeOPE5XNNhn6bxRAmVswsPZ0wZCmzVvP8Tl/LZK9ofVmRVGzll
srU1uezJEyHKYdOHrE2p52xUj+pHzjJvb6AHBgUrgQQAI6GBiQOBhgAEAAofUz1J
hBSOyGHLsnV9Sz0DSWmhl7U+ljqbfa8PKVFWSV3w16I1v2zME5/UzUhHn1gWsjnv
7/ekcLLAQbvqMPNXAfjIhFXLAPzqbB9iCuVua1v0Vgy52rBemOWrJka/Ws2bnKR8
h1N1OxOYeYr6C2jqMygBLktKMAs+282CEiEb4bIv
-----END EC PRIVATE KEY-----"; 
```
  

### Formatar data
O objeto de data e hora informado deve estar em UTC e deve seguir o padrão da norma internacional ISO 8601 ("2023-06-26T19:48:32.759844Z")

**Python**

```python
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
```
  

**PHP**

```php
$timestamp = gmdate('Y-m-d\TH:i:s.u\Z');
```
  

**Node.js**

```js
const timestamp = new Date().toISOString();
```
  

**Java**

```java
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'");
String formattedDate = sdf.format(now);
```
  

**C#**

```c#
var timestamp = datetime.now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
```
  

### Definir cabeçalho JWT
Definimos o algoritmo de codificação JWT

**Python**

```python
jwt_header = {
    "typ": "JWT",
    "alg": "ES512"
}
```
  

**PHP**

```php
$header = [
    "typ" => "JWT",
    "alg" => "ES512"
];
```
  

**Node.js**

```js
const jwt_header = {
  typ: 'JWT',
  alg: 'ES512'
};
```
  

**Java**

```java
// Não é necessário
```
  

**C#**

```c#
var jwt_header = new Dictionary<string, object>
{ 
  { "typ", "JWT" },
  { "alg", "ES512" }
};
```
  

### Construir hash em MD5 para assinatura no cabeçalho JSON

Construir hash em MD5 para assinatura no cabeçalho (header) utilizando o payload

**Python**

```python
json_body = json.dumps(request_body)
md5_hash = md5(json_body.encode()).hexdigest()
```
  

**PHP**

```php
$request_body_json = json_encode($request_body);
$md5_hash = md5($request_body_json);
```
  

**Node.js**

```js
const str_body = JSON.stringify(request_body);
const md5_hash = crypto.createHash('md5').update(str_body).digest('hex');
```
  

**Java**

```java
String payloadMd5 = md5Hash(jsonToString(request_body));

...

private static String jsonToString(Map<String, Object> jsonMap) {
    return new com.google.gson.Gson().toJson(jsonMap);
}

...

private static String md5Hash(String text) {
    try {
        java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
        byte[] array = md.digest(text.getBytes());
        StringBuilder sb = new StringBuilder();
        for (byte b : array) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    } catch (java.security.NoSuchAlgorithmException e) {
        return null;
    }
}
```
  

**C#**

```c#
var json_body = JsonConvert.SerializeObject(request_body);
var md5_hash = CalculateMD5Hash(json_body);

.

static string CalculateMD5Hash(string input)
{
    using (MD5 md5 = MD5.Create())
    {
        byte[] inputBytes = Encoding.UTF8.GetBytes(input);
        byte[] hashBytes = md5.ComputeHash(inputBytes);

        StringBuilder builder = new StringBuilder();

        for (int i = 0; i < hashBytes.Length; i++)
        {
            builder.Append(hashBytes[i].ToString("x2"));
        }

        return builder.ToString();
    }
}
```
  

:::caution Atenção!

O hash md5 de requisições dos métodos GET e DELETE deve ser gerado com o payload vazio
:::

### Construir hash em MD5 para assinatura no cabeçalho Arquivo

Construir hash em MD5 para assinatura no cabeçalho (header) utilizando um arquivo

**Python**

```python
md5_instance = md5()
for chunk in iter(lambda: file.read(4096), b""):
    md5_instance.update(chunk)

file.seek(0)
md5_hash = md5_instance.hexdigest()
```

**PHP**

```php
$md5_instance = md5_file($file);
$md5_hash = hash_file('md5', $file);
```

**Node.js**

```js
const md5_instance = crypto.createHash('md5');
const readStream = fs.createReadStream(file);

readStream.on('data', (chunk) => {
  md5_instance.update(chunk);
});

readStream.on('end', () => {
  const md5_hash = md5_instance.digest('hex');
  file.seek(0);
```

**Java**

```java
MessageDigest md5_instance = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[4096];
int bytesRead;

try (InputStream inputStream = new FileInputStream(file)) {
    while ((bytesRead = inputStream.read(buffer)) != -1) {
        md5_instance.update(buffer, 0, bytesRead);
    }
}

byte[] md5_hashBytes = md5_instance.digest();
StringBuilder md5_hashBuilder = new StringBuilder();

for (byte b : md5_hashBytes) {
    md5_hashBuilder.append(String.format("%02x", b));
}

String md5_hash = md5_hashBuilder.toString();
```

**C#**

```c#
using (var md5_instance = MD5.Create())
{
    using (var stream = File.OpenRead(file))
    {
        byte[] hash = md5_instance.ComputeHash(stream);
        string md5_hash = BitConverter.ToString(hash).Replace("-", "").ToLower();
        stream.Seek(0, SeekOrigin.Begin);
    }
}
```

### Definir o corpo do JWT
Essas são as infromações necessárias para assinatura do cabeçalho

**Python**

```python
jwt_body = {
    "payload_md5": md5_hash,
    "timestamp": timestamp,
    "method": method,
    "uri": endpoint
}
```
  

**PHP**

```php
$payload = [
    "payload_md5" => $md5_hash,
    "timestamp" => $timestamp,
    "method" => $method,
    "uri" => $endpoint
];
```
  

**Node.js**

```js
const jwt_body = {
  payload_md5: md5_hash,
  timestamp: timestamp,
  method: method,
  uri: endpoint
};
```
  

**Java**

```java
Map<String, Object> jwt_body = new HashMap<>();
jwt_body.put("payload_md5", payloadMd5);
jwt_body.put("timestamp", formattedDate);
jwt_body.put("method", method);
jwt_body.put("uri", endpoint);
```
  

**C#**

```c#
// Ajuste: Remover espaços e quebras de linha no meio da chave privada
client_private_key = client_private_key.Replace("-----BEGIN EC PRIVATE KEY-----", "")
                                       .Replace("-----END EC PRIVATE KEY-----", "")
                                       .Replace("\n", "")
                                       .Replace("\r", "");
// Converter a chave privada para ECDsa
using (ECDsa ecdsa = ECDsa.Create())
{
  ecdsa.ImportECPrivateKey(Convert.FromBase64String(client_private_key), out _);
  var jwt_body = new Dictionary<string, object>
    {
      { "payload_md5", md5_hash },
      { "timestamp", timestamp },
      { "method", method },
      { "uri", endpoint }
    };
```
  

### Realizar criptografia do header

**Python**

```python
encoded_header_token = jwt.encode(
    claims=jwt_body,
    key=client_private_key,
    algorithm="ES512",
    headers=jwt_header
)
```
  

**PHP**

```php
$jws = $jwsBuilder
    ->create()
    ->withPayload(json_encode($payload))
    ->addSignature($privateKey, $header)
    ->build();
$serializer = new CompactSerializer();
$jwt = $serializer->serialize($jws, 0);
```
  

**Node.js**

```js
const encoded_header_token = jwt.sign(
  jwt_body,
  client_private_key,
  {
    algorithm: 'ES512',
    header: jwt_header
  }
);
```
  

**Java**

```java
PrivateKey privateKey = getPrivateKey(clientPrivateKey);
JwtBuilder jwtBuilder = Jwts.builder().setClaims(jwt_body).signWith(privateKey, SignatureAlgorithm.ES512);
String encodedHeaderToken = jwtBuilder.compact();

...

public static PrivateKey getPrivateKey(final String encodedPvKey) {
    try {
        final String pvKey = new String(Base64.getDecoder().decode(encodedPvKey));
        PEMParser pemParser = new PEMParser(new StringReader(pvKey));
        PEMKeyPair pemKeyPair = (PEMKeyPair) pemParser.readObject();

        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        KeyPair kp = converter.getKeyPair(pemKeyPair);
        pemParser.close();

        return kp.getPrivate();
    } catch (IOException e) {
        throw new RuntimeException("Couldn't load private key");
    }
}
```
  

**C#**

```c#
var encoded_header_token = JWT.Encode(jwt_body, ecdsa, JwsAlgorithm.ES512, jwt_header);
```
  

### Montar header assinado

**Python**

```python
signed_header = {
    "AUTHORIZATION": encoded_header_token,
    "API-CLIENT-KEY": api_key
}
```
  

**PHP**

```php
$headers = [
    'Authorization' => $jwt,
    'API-CLIENT-KEY' => $api_key,
];
```
  

**Node.js**

```js
const signed_header = {
  AUTHORIZATION: encoded_header_token,
  'API-CLIENT-KEY': api_key
};
```
  

**Java**

```java
Map<String, String> headers = new HashMap<>();
headers.put("AUTHORIZATION", encodedHeaderToken);
headers.put("API-CLIENT-KEY", api_key);
```
  

**C#**

```c#
using (var client = new HttpClient())
    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Add("AUTHORIZATION", encoded_header_token);
    client.DefaultRequestHeaders.Add("API-CLIENT-KEY", api_key);
```
  

### Construir a URL da solicitação

**Python**

```python
url = f"{base_url}{endpoint}"
```
  

**PHP**

```php
$url = $base_url . $endpoint;
```
  

**Node.js**

```js
const url = `${base_url}${endpoint}`;
```
  

**Java**

```java
String requestUrl = base_url + endpoint;
```
  

**C#**

```c#
var url = $"{base_url}{endpoint}";
```
  

## 3. Realizar Requisição

**Python**

```python
post_test_response = requests.post(url=url, headers=signed_header, json=request_body)
```
  

**PHP**

```php
$response = \WpOrg\Requests\Requests::post($url, $headers, json_encode($request_body));
```
  

**Node.js**

```js
axios
  .post(url, request_body, { headers: signed_header })
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.error(error);
  });
```
  

**Java**

```java
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");
okhttp3.RequestBody requestBody = RequestBody.create(mediaType, jsonToString(request_body));
Request request = new Request.Builder().url(requestUrl).headers(okhttp3.Headers.of(headers))
        .method(method, requestBody).build();
Response response = client.newCall(request).execute();

System.out.println(response.body().string());
```
  

**C#**

```c#
var content = new StringContent(json_body, Encoding.UTF8, "application/json");
var post_test_response = client.PostAsync(url, content).Result;
```

---

# Validação de Webhooks

URL: /documentation/primeiros_passos/teste_de_autenticacao/webhook_v2

:::info Veja também
- [Configurando Webhooks](/documentation/primeiros_passos/configurando_webhooks)
:::

## 1. Introdução e Preparação

### Visão Geral e Importância
Esta seção aborda como a QI Tech envia webhooks com headers assinados, destacando a importância de descriptografar e validar esses headers para garantir segurança nas comunicações.

### Formato das Requisições
As requisições de webhook serão enviadas para a [URL configurada para recebimento dos webhooks](/documentation/primeiros_passos/configurando_webhooks). Elas possuem um formato específico de headers e body, detalhado a seguir.

ENDPOINT URL configurada para recebimento dos webhooks
MÉTODO POST

Request Headers

```json
{
    "AUTHORIZATION": "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkX21kNSI6IjRhNjAzZjBmMGU3ZGRkZTlkYTJhMGFkM2QzNDFmNzRiIiwidGltZXN0YW1wIjoiMjAyMy0wNi0zMFQxODo1MjoyNy44ODU3MzFaIiwibWV0aG9kIjoiUE9TVCIsInVyaSI6Ii90ZXN0In0.AcNiJqXDdVmlXSbPI6bH41n0KXz9JwVVMgo4Ivqsq5UZjM2WBOTWw3aAvIMAAhjK5OdrURD4cX3dbbnRgzxspUckANRt0hVHRKSkhROHBfZxuTXVfv8oYzwghwiO2MatPBsroC9Vxbh-DEVQJIBigtN9_D5bg8p2-mlVvoxou2I-EwZs",
    "API-CLIENT-KEY": "20d6a816-9d21-4e29-bbe5-2ffb3baacfe9"
}
```

Request Body

```json
{
    "body_sample": "Exemplo de webhook"
}
```

## 2. Configuração e Descriptografia

### Importar bibliotecas

Antes de começar a descriptografia e validação dos webhooks, é essencial importar as bibliotecas necessárias em sua linguagem de programação preferida. Estas bibliotecas facilitarão o trabalho com JWTs, criptografia e outros aspectos relacionados.

**Python**

```python
import json
from datetime import datetime, timedelta
from hashlib import md5
from jose import jwt
```

**PHP**

```php
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Signature\Algorithm\ES512;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\Serializer\JWSSerializerManager;
use Jose\Component\Signature\Serializer\CompactSerializer;
```

**Node.js**

```js
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
```

**Java**

```java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
```

**C#**

```c#
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using Jose;
```

### Definir variáveis

Defina as variáveis necessárias para manipular os headers e o corpo do webhook. Isso inclui a chave pública fornecida pela QI Tech, utilizada para descriptografar e validar o webhook.

**Python**

```python
headers = {
    "AUTHORIZATION": "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkX21kNSI6IjRhNjAzZjBmMGU3ZGRkZTlkYTJhMGFkM2QzNDFmNzRiIiwidGltZXN0YW1wIjoiMjAyMy0wNi0zMFQxODo1MjoyNy44ODU3MzFaIiwibWV0aG9kIjoiUE9TVCIsInVyaSI6Ii90ZXN0In0.AcNiJqXDdVmlXSbPI6bH41n0KXz9JwVVMgo4Ivqsq5UZjM2WBOTWw3aAvIMAAhjK5OdrURD4cX3dbbnRgzxspUckANRt0hVHRKSkhROHBfZxuTXVfv8oYzwghwiO2MatPBsroC9Vxbh-DEVQJIBigtN9_D5bg8p2-mlVvoxou2I-EwZs",
    "API-CLIENT-KEY": "20d6a816-9d21-4e29-bbe5-2ffb3baacfe9",
}
body = {"body_sample": "Exemplo de webhook"}
authorization = headers.get("AUTHORIZATION")
```

**PHP**

```php
$headers = [
    "AUTHORIZATION" => "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkX21kNSI6IjRhNjAzZjBmMGU3ZGRkZTlkYTJhMGFkM2QzNDFmNzRiIiwidGltZXN0YW1wIjoiMjAyMy0wNi0zMFQxODo1MjoyNy44ODU3MzFaIiwibWV0aG9kIjoiUE9TVCIsInVyaSI6Ii90ZXN0In0.AcNiJqXDdVmlXSbPI6bH41n0KXz9JwVVMgo4Ivqsq5UZjM2WBOTWw3aAvIMAAhjK5OdrURD4cX3dbbnRgzxspUckANRt0hVHRKSkhROHBfZxuTXVfv8oYzwghwiO2MatPBsroC9Vxbh-DEVQJIBigtN9_D5bg8p2-mlVvoxou2I-EwZs",
    "API-CLIENT-KEY" => "20d6a816-9d21-4e29-bbe5-2ffb3baacfe9",
];
$body = ["body_sample" => "Exemplo de webhook"];
$authorization = $headers["AUTHORIZATION"];
```

**Node.js**

```js
const headers = {
  AUTHORIZATION: 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkX21kNSI6IjRhNjAzZjBmMGU3ZGRkZTlkYTJhMGFkM2QzNDFmNzRiIiwidGltZXN0YW1wIjoiMjAyMy0wNi0zMFQxODo1MjoyNy44ODU3MzFaIiwibWV0aG9kIjoiUE9TVCIsInVyaSI6Ii90ZXN0In0.AcNiJqXDdVmlXSbPI6bH41n0KXz9JwVVMgo4Ivqsq5UZjM2WBOTWw3aAvIMAAhjK5OdrURD4cX3dbbnRgzxspUckANRt0hVHRKSkhROHBfZxuTXVfv8oYzwghwiO2MatPBsroC9Vxbh-DEVQJIBigtN9_D5bg8p2-mlVvoxou2I-EwZs',
  'API-CLIENT-KEY': '20d6a816-9d21-4e29-bbe5-2ffb3baacfe9'
};
const body = { body_sample: 'Exemplo de webhook' };
```

**Java**

```java
String authorization = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkX21kNSI6IjRhNjAzZjBmMGU3ZGRkZTlkYTJhMGFkM2QzNDFmNzRiIiwidGltZXN0YW1wIjoiMjAyMy0wNi0zMFQxODo1MjoyNy44ODU3MzFaIiwibWV0aG9kIjoiUE9TVCIsInVyaSI6Ii90ZXN0In0.AcNiJqXDdVmlXSbPI6bH41n0KXz9JwVVMgo4Ivqsq5UZjM2WBOTWw3aAvIMAAhjK5OdrURD4cX3dbbnRgzxspUckANRt0hVHRKSkhROHBfZxuTXVfv8oYzwghwiO2MatPBsroC9Vxbh-DEVQJIBigtN9_D5bg8p2-mlVvoxou2I-EwZs";
```

**C#**

```c#
var headers = new Dictionary<string, string>()
{
    { "AUTHORIZATION", "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkX21kNSI6IjRhNjAzZjBmMGU3ZGRkZTlkYTJhMGFkM2QzNDFmNzRiIiwidGltZXN0YW1wIjoiMjAyMy0wNi0zMFQxODo1MjoyNy44ODU3MzFaIiwibWV0aG9kIjoiUE9TVCIsInVyaSI6Ii90ZXN0In0.AcNiJqXDdVmlXSbPI6bH41n0KXz9JwVVMgo4Ivqsq5UZjM2WBOTWw3aAvIMAAhjK5OdrURD4cX3dbbnRgzxspUckANRt0hVHRKSkhROHBfZxuTXVfv8oYzwghwiO2MatPBsroC9Vxbh-DEVQJIBigtN9_D5bg8p2-mlVvoxou2I-EwZs" },
    { "API-CLIENT-KEY", "20d6a816-9d21-4e29-bbe5-2ffb3baacfe9" }
};

var body = new Dictionary<string, string>()
{
    { "body_sample", "Exemplo de webhook" }
};
```

### 2. Inserção de Dados de Criptografia e Realização da Descriptografia
Inserimos a chave pública fornecida pela QI Tech e realizamos a descriptografia do header do webhook. Essa chave é crucial para a descriptografia dos headers do webhook.

**Python**

```python
qi_public_key = """-----BEGIN PUBLIC KEY-----
{QI_PUBLIC_KEY}
-----END PUBLIC KEY-----"""
```

**PHP**

```php
$qiPublicKey = "-----BEGIN PUBLIC KEY-----
{QI_PUBLIC_KEY}
-----END PUBLIC KEY-----";
```

**Node.js**

```js
const qiPublicKey = `-----BEGIN PUBLIC KEY-----
{QI_PUBLIC_KEY}
-----END PUBLIC KEY-----`;
```

**Java**

```java
String publicKeyStr = "{QI_PUBLIC_KEY}";
```

**C#**

```c#
var authorization = headers["AUTHORIZATION"];

var qiPublicKey = @"-----BEGIN PUBLIC KEY-----
{QI_PUBLIC_KEY}
-----END PUBLIC KEY-----";
```

### Realizar descriptografia do header

O processo de descriptografia é essencial para verificar a autenticidade e integridade do webhook recebido.

**Python**

```python
try:
    decoded_header = jwt.decode(token=authorization, key=qi_public_key)
except:
    raise Exception("Decodification failed.")
```

**PHP**

```php
$algorithmManager = new AlgorithmManager([new ES512()]);
$jwsVerifier = new JWSVerifier($algorithmManager);
$publicKey = JWKFactory::createFromKey($qiPublicKey, null, ['use' => 'sig']);
$serializerManager = new JWSSerializerManager([new CompactSerializer]);
$jws = $serializerManager->unserialize($authorization);
$decodedHeader = json_decode($jws->getPayload(), true);
```

**Node.js**

```js
const decodedHeader = jwt.verify(authorization, qiPublicKey);
```

**Java**

```java
private static Claims validate(final String encodedBody, String publicKeyStr){
    try {
        Security.addProvider(new BouncyCastleProvider());

        final String pbKey = new String(Base64.getDecoder().decode(publicKeyStr));
        Reader rdr = new StringReader(pbKey);
        PemReader pemParser = new PemReader(rdr);

        X509EncodedKeySpec spec = new X509EncodedKeySpec(pemParser.readPemObject().getContent());
        KeyFactory kf = KeyFactory.getInstance("EC");

        PublicKey publicKey = kf.generatePublic(spec);
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(encodedBody).getBody();
    }  catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
        throw new IllegalStateException(e);
    }
}
```

**C#**

```c#
var key = ECDsa.Create();
key.ImportFromPem(qiPublicKey);
var decodedHeader = JWT.Decode<IDictionary<string, string>>(authorization, key);
```

## 3. Validação e Conclusão

### Realização de Validações

Após descriptografar o header, é importante realizar várias validações para garantir que o webhook é válido e seguro.

**Python**

```python
assert decoded_header.get("method") == "POST"
assert decoded_header.get("uri") == "/client_webhook_endpoint"
assert (
    decoded_header.get("payload_md5")
    == md5(json.dumps(body).encode()).hexdigest()
)
assert (
    (datetime.now() - timedelta(minutes=5))
    < datetime.strptime(decoded_header.get("timestamp"), "%Y-%m-%dT%H:%M:%S.%fZ")
    < (datetime.now() + timedelta(minutes=5))
)
```

**PHP**

```php
$method = $decodedHeader["method"];
$uri = $decodedHeader["uri"];
$payloadMd5 = $decodedHeader["payload_md5"];
$timestamp = $decodedHeader["timestamp"];

assert($method === "POST");
assert($uri === "/client_webhook_endpoint");
assert($payloadMd5 === md5(json_encode($body, JSON_UNESCAPED_SLASHES)));
assert(
    (new DateTime("now", new DateTimeZone("UTC")))->sub(new DateInterval("PT5M")) < DateTime::createFromFormat("Y-m-d\TH:i:s.u\Z", $timestamp) &&
    DateTime::createFromFormat("Y-m-d\TH:i:s.u\Z", $timestamp) < (new DateTime("now", new DateTimeZone("UTC")))->add(new DateInterval("PT5M"))
);
```

**Node.js**

```js
if (decodedHeader.method !== 'POST') {
    throw new Error('Invalid method');
  }
  
  if (decodedHeader.uri !== '/client_webhook_endpoint') {
    throw new Error('Invalid URI');
  }
  
  const payloadMd5 = crypto
    .createHash('md5')
    .update(JSON.stringify(body))
    .digest('hex');
    
  if (decodedHeader.payload_md5 !== payloadMd5) {
    throw new Error('Invalid payload MD5');
  }
  
  const timestamp = new Date(decodedHeader.timestamp);
  const currentDateTime = new Date();
  
  const fiveMinutesAgo = new Date(currentDateTime.getTime() - 5 * 60000);
  const fiveMinutesAhead = new Date(currentDateTime.getTime() + 5 * 60000);
  
  if (!(timestamp > fiveMinutesAgo && timestamp < fiveMinutesAhead)) {
    throw new Error('Invalid timestamp');
  }
```

**Java**

```java
Claims result = validate(authorization, publicKeyStr);
System.out.println(result);
System.out.println(result.get("method").equals("POST"));
System.out.println(result.get("uri").equals("/test"));
```

**C#**

```c#
var method = decodedHeader["method"];
var uri = decodedHeader["uri"];
var payloadMd5 = decodedHeader["payload_md5"];
var timestamp = DateTime.Parse(decodedHeader["timestamp"]);
timestamp = timestamp.ToUniversalTime();
var bodyJson = JsonConvert.SerializeObject(body, new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    Formatting = Formatting.None
});

using (var md5Hash = MD5.Create())
{
    var calculatedMd5 = GetMd5Hash(md5Hash, bodyJson);
    if (payloadMd5 != calculatedMd5)
    {
        throw new Exception("Payload MD5 verification failed.");
    }
}

var currentTime = datetime.now;
var validTimeStart = currentTime.AddMinutes(-5);
var validTimeEnd = currentTime.AddMinutes(5);
if (timestamp < validTimeStart || timestamp > validTimeEnd)
{
    throw new Exception("Timestamp verification failed.");
}

...

static string GetMd5Hash(MD5 md5Hash, string input)
{
    byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));

    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < data.Length; i++)
    {
        builder.Append(data[i].ToString("x2"));
    }

    return builder.ToString();
}
```

---

# Troca de chaves

URL: /documentation/primeiros_passos/troca_de_chaves

## Geração do par de chaves (pública e privada)

**Unix**

Para gerar sua chave privada em um computador **Unix** faça em seu terminal ou linha de comando:

```bash
$ ssh-keygen -t ecdsa -b 521 -m PEM -f jwtECDSASHA512.key
```

E a partir desta chave privada gere sua chave pública.

```bash
$ openssl ec -in jwtECDSASHA512.key -pubout -outform PEM -out jwtECDSASHA512.key.pub
```

**Mac OS**

Para gerar sua chave privada em um computador **Mac OS** rode em seu terminal:

```bash
openssl ecparam -name secp521r1 -genkey -noout -out ec512-private.pem
```

E a partir desta chave privada gere sua chave pública.

```bash
openssl ec -in ec512-private.pem -pubout -out ec512-public.pem
```

**Windows**

Para gerar seu par de chaves (privada e pública) em um computador **Windows**, você precisará das ferramentas `ssh-keygen` e `openssl` no PowerShell ou GitBash.

Execute o comando abaixo para criar o arquivo da sua chave privada (`jwtECDSASHA512.key`).

```bash
ssh-keygen -t ecdsa -b 521 -m PEM -f jwtECDSASHA512.key
```

E a partir desta chave privada gere sua chave pública (`jwtECDSASHA512.key.pub`).

```bash
openssl ec -in jwtECDSASHA512.key -pubout -outform PEM -out jwtECDSASHA512.key.pub
```

Para visualizar a chave em um bloco de notas:

```bash
notepad jwtECDSASHA512.key.pub
```

Para visualizar a chave no terminal:

```bash
cat jwtECDSASHA512.key.pub
```

Caso você adicione uma senha de criptografia em sua chave privada e deseje visualizá-la, será necessário descriptografar com o comando abaixo e sua senha:

```bash
openssl ec -in jwtECDSASHA512.key -out chave_descriptografada.pem
```