# QI Tech — Insurance-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:
- Autenticação (/documentation/seguros/introducao/autenticacao)
- Introdução (/documentation/seguros/introducao/inicio)
- Configurando Webhooks (/documentation/seguros/primeiros_passos/seguros_configurando_webhooks)
- Configurar IP de Integração (/documentation/seguros/primeiros_passos/seguros_configurar_ip_de_integracao)
- Troca de chaves (/documentation/seguros/primeiros_passos/seguros_troca_de_chaves)
- Possíveis erros (/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_possiveis_erros)
- Exemplo completo de teste de autenticação (/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_teste_de_autenticacao_completo)
- Teste de autenticação (/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_teste_de_autenticacao_v2)
- Validação de Webhooks (/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_webhook_v2)

---

# Autenticação

URL: /documentation/seguros/introducao/autenticacao

A autenticação do Insurance-as-a-Service segue o **padrão de requisição assinada da QI Tech** — o mesmo utilizado no Lending-as-a-Service, no Banking-as-a-Service e na QI DTVM. Se você já integra qualquer outra linha de produto QI Tech, o mecanismo é idêntico; muda apenas o host.

## Requisição assinada

Todas as requisições devem usar **HTTPS** com **TLS 1.2 ou 1.3** e conter dois headers:

| Header           | Descrição                                                                                    |
| ---------------- | -------------------------------------------------------------------------------------------- |
| `API-CLIENT-KEY` | Chave disponibilizada pelo time de Integração da QI Tech que identifica a sua integração.     |
| `AUTHORIZATION`  | Assinatura da requisição no padrão JWT, gerada com a sua chave privada.                       |

A QI Tech utiliza o padrão de chaves assimétricas: você gera um par de chaves, assina cada requisição com a sua **chave privada** e a QI Tech valida a assinatura com a sua **chave pública**.

O passo a passo completo — geração do par de chaves, envio da chave pública à QI Tech e montagem do header `AUTHORIZATION` — está descrito em:

- [Troca de Chaves](/documentation/seguros/primeiros_passos/seguros_troca_de_chaves)
- [Teste de Autenticação](/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_teste_de_autenticacao_v2)
- [Exemplo Completo de Autenticação](/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_teste_de_autenticacao_completo)
- [Possíveis Erros de Autenticação](/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_possiveis_erros)

:::caution Atenção
A chave privada é de uso exclusivo do parceiro integrador e deve ser armazenada com segurança. A QI Tech nunca irá pedir, em hipótese alguma, que você a compartilhe.
:::

Opcionalmente, você pode restringir as chamadas da sua integração a uma lista de endereços IP autorizados. Veja [Configurar IP de Integração](/documentation/seguros/primeiros_passos/seguros_configurar_ip_de_integracao).

## Recebimento de webhooks

Os eventos de pedido e apólice são notificados na URL de callback configurada para a sua integração. Para configurar a URL, siga [Configurando Webhooks](/documentation/seguros/primeiros_passos/seguros_configurando_webhooks) e valide o recebimento com [Validação de Webhooks](/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_webhook_v2).

Os webhooks da QI Tech seguem uma estrutura padrão:

```json
{
  "webhook_type": "<tipo_do_evento>",
  "webhook_datetime": "2026-07-16T14:03:22Z",
  "data": {}
}
```

| Campo              | Tipo   | Descrição                                   |
| ------------------ | ------ | ------------------------------------------- |
| `webhook_type`     | string | Identificador do tipo de evento.            |
| `webhook_datetime` | string | Data e hora do evento no formato ISO 8601.  |
| `data`             | object | Dados específicos do evento.                |

Os eventos disponíveis nesta linha de produto estão descritos em [Webhooks de Pedido](/documentation/seguros/pedidos/webhooks) e [Webhooks de Apólice](/documentation/seguros/apolices/webhooks).

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

---

# Introdução

URL: /documentation/seguros/introducao/inicio

O **Insurance-as-a-Service** da QI Tech permite que parceiros distribuam produtos de seguro por API: consulta do catálogo de produtos habilitados, cotação, venda (pedido com aceite e pagamento do segurado), acompanhamento das apólices emitidas e do repasse financeiro das comissões.

Essa documentação descreve os fluxos, endpoints e estruturas de dados da jornada completa de distribuição de seguros.

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.

## Visão geral da jornada

1. **Catálogo** — consulte os produtos de seguro que a sua integração está habilitada a distribuir, com as coberturas, limites e a sua faixa de comissão ([Catálogo de Produtos](/documentation/seguros/catalogo/inicio)).
2. **Cotação** — precifique uma seleção de produtos e coberturas para um cliente, sem criar nenhum recurso ([Criar cotação](/documentation/seguros/cotacao/criar_cotacao)), e descubra a faixa de preço vendável de cada produto ([Simular faixa de preço](/documentation/seguros/cotacao/simular_precos)).
3. **Pedido** — submeta a venda, já com o **aceite** que você coletou do segurado. O pedido nasce aguardando o **pagamento** da primeira parcela; confirmado o pagamento, o pedido é emitido ([Pedidos](/documentation/seguros/pedidos/inicio)).
4. **Apólices** — a emissão do pedido gera uma apólice por produto vendido, emitida junto à seguradora. Consulte e cancele apólices individualmente ([Apólices](/documentation/seguros/apolices/inicio)). _Superfície ainda não publicada._
5. **Financeiro** — acompanhe o seu saldo de comissão, o extrato de movimentações e os repasses realizados ([Financeiro](/documentation/seguros/financeiro/inicio)). _Superfície ainda não publicada._

Cada etapa relevante notifica a sua URL de callback por [webhooks de pedido](/documentation/seguros/pedidos/webhooks) e [webhooks de apólice](/documentation/seguros/apolices/webhooks).

## Ambientes (Hosts)

O Insurance-as-a-Service possui dois ambientes, SANDBOX e PRODUÇÃO. Ambos possuem comportamento idêntico, porém o ambiente de SANDBOX opera com valores e emissões totalmente fictícios, enquanto o de Produção realiza transações e emissões válidas.

| Ambiente | Host                                       |
| -------- | ------------------------------------------ |
| Sandbox  | https://api.sandbox.insurance.qitech.app   |
| Produção | https://api.insurance.qitech.app           |

:::danger Aviso Importante!
Não devem ser usados dados reais de pessoas físicas e/ou jurídicas nos ambientes de Sandbox da QI Tech.
:::

## Convenções da API

- Os paths são versionados com o segmento de versão **liderando** o caminho (ex.: `/v1/insurance/order`, `/v1/product_catalog/products`).
- Todos os recursos são endereçados por chaves públicas UUID (`order_key`, `policy_key`, `product_key`), nunca por identificadores numéricos internos.
- Os paths de coleção nem sempre são plurais: a criação e a listagem de pedidos vivem em `/v1/insurance/order` (singular), enquanto as rotas por chave usam `/v1/insurance/orders/{order_key}`. Siga o path indicado na página de cada endpoint.
- Valores monetários trafegam como **número JSON com 2 casas decimais** (ex.: `312.48`, nunca string), sempre em BRL e sempre **brutos** (com IOF). Taxas e percentuais são números com 4 casas em `(0, 1]` (ex.: `0.1000`). Identificadores numéricos com zeros à esquerda significativos (documentos, códigos de ramo) permanecem strings.
- Status são strings de enumerador em caixa baixa (ex.: `awaiting_payment`, `emitted`). Não mapeie o conjunto de forma fechada — veja os [status reservados](/documentation/seguros/pedidos/inicio).
- Vigências são objetos aninhados `term: { "start_date", "end_date" }` — por produto e por cobertura.
- Datas seguem `AAAA-MM-DD` e data-hora segue ISO 8601 (`2026-07-16T14:03:22Z`).
- Listagens usam paginação por offset, mas **a forma varia por superfície** — confira sempre a página do endpoint. Pedidos usam `page` / `page_size` (padrão `50`, máximo `200`) e devolvem `{ items, page, page_size, total }`; o catálogo de produtos usa `page` / `rows_per_page` (padrão `50`) e devolve `{ data, pagination { current_page, next_page, rows_per_page } }`, sem `total`.
- Todo erro (`non-2xx`) retorna o corpo padrão `{ "title", "description", "translation", "code" }`. O `code` é o **único** campo para tratamento programático — `title`/`description`/`translation` podem mudar sem aviso.
- O escopo de acesso é sempre o da sua integração: uma chave de outro parceiro é indistinguível de uma chave inexistente e retorna `404`.

## Para começar

A autenticação segue o padrão QI Tech de requisições assinadas, o mesmo utilizado nas demais linhas de produto. Veja [Autenticação](/documentation/seguros/introducao/autenticacao).

Antes de consumir os endpoints desta documentação, complete os passos abaixo **em ambiente de sandbox**:

1. Entrar em contato com o time de Integração ([api@qitech.com.br](mailto:api@qitech.com.br)) para iniciar o onboarding da sua integração.
2. [Gerar o par de chaves e enviar a sua chave pública por meio seguro](/documentation/seguros/primeiros_passos/seguros_troca_de_chaves) para receber as credenciais de integração.
3. [Realizar o teste de autenticação](/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_teste_de_autenticacao_v2).
4. [Configurar a URL de recebimento de webhooks](/documentation/seguros/primeiros_passos/seguros_configurando_webhooks).

---

# Configurando Webhooks

URL: /documentation/seguros/primeiros_passos/seguros_configurando_webhooks

:::info Veja também
- [Validação de Webhooks](/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_webhook_v2)
- [Configurar IP de Integração](/documentation/seguros/primeiros_passos/seguros_configurar_ip_de_integracao)
:::

Os eventos de pedido e de apólice são notificados por `POST` na URL de callback configurada para a sua integração.

## Como configurar

No Insurance-as-a-Service a URL de recebimento das notificações **não está disponível pelo portal QI Tech**. Ela é configurada pelo time de Integração da QI Tech.

Envie para [api@qitech.com.br](mailto:api@qitech.com.br):

- A **URL de callback**, por ambiente (sandbox e produção). A URL deve ser HTTPS e aceitar `POST`.
- Os **headers** que você precisa que a QI Tech envie nas notificações, se houver — por exemplo um header de autenticação do seu lado.

Avise-nos com antecedência quando a URL mudar: enquanto a alteração não for registrada, as notificações continuam sendo enviadas para o endereço anterior.

## Formato das notificações

Os webhooks da QI Tech seguem uma estrutura padrão:

```json
{
  "webhook_type": "<tipo_do_evento>",
  "webhook_datetime": "2026-07-16T14:03:22Z",
  "data": {}
}
```

| Campo              | Tipo   | Descrição                                   |
| ------------------ | ------ | ------------------------------------------- |
| `webhook_type`     | string | Identificador do tipo de evento.            |
| `webhook_datetime` | string | Data e hora do evento no formato ISO 8601.  |
| `data`             | object | Dados específicos do evento.                |

Os eventos desta linha de produto estão descritos em [Webhooks de Pedido](/documentation/seguros/pedidos/webhooks) e [Webhooks de Apólice](/documentation/seguros/apolices/webhooks).

As notificações são enviadas com headers assinados. Valide a assinatura antes de processar o conteúdo — veja [Validação de Webhooks](/documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_webhook_v2).

:::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 Informação
O timeout para resposta dos nossos webhooks é de 10 segundos.
:::

---

# Configurar IP de Integração

URL: /documentation/seguros/primeiros_passos/seguros_configurar_ip_de_integracao

:::info Veja também
- [Configurando Webhooks](/documentation/seguros/primeiros_passos/seguros_configurando_webhooks)
- [Troca de Chaves](/documentation/seguros/primeiros_passos/seguros_troca_de_chaves)
:::

As chamadas à API do Insurance-as-a-Service são restritas a uma lista de **endereços IP públicos** previamente autorizados para a sua integração. Uma requisição originada de um IP fora da lista é recusada, por mais correta que seja a sua assinatura.

## Como configurar

No Insurance-as-a-Service o cadastro dos IPs **não está disponível pelo portal QI Tech**. A lista é registrada pelo time de Integração da QI Tech.

Envie a relação dos endereços IP públicos de onde a sua integração fará as chamadas para [api@qitech.com.br](mailto:api@qitech.com.br), informando o ambiente (sandbox ou produção) de cada endereço.

O que é aceito em cada entrada:

- Um endereço IPv4 público, individual (ex.: `189.10.20.30`)
- Um endereço IPv6 público, individual

Cada endereço é comparado exatamente como foi cadastrado, portanto informe **um endereço por entrada** — inclusive quando eles forem vizinhos na mesma faixa.

:::caution Envie a lista completa desde o início
Informe todos os endereços de uma vez no onboarding da sua integração. Incluir um endereço depois exige uma nova solicitação ao time de Integração, e até que ela seja processada as chamadas partindo dele são recusadas.

Avise-nos **antes** de passar a chamar a API a partir de uma rede nova — um novo escritório, uma VPN, um NAT gateway adicional ou um _runner_ de CI. O cadastro passa a valer imediatamente após ser registrado, mas só depois de ser registrado.
:::

## Possíveis erros

Todo erro (`non-2xx`) retorna o corpo padrão de erro — trate programaticamente **apenas** o campo `code`.

| Status | Código | Descrição |
|---|---|---|
| `403` | `EGW000007` | O IP de origem da requisição não está na lista de IPs permitidos da integração. |

```json
{
  "title": "Forbidden",
  "description": "The request source IP is not in the integration's allowed IP list.",
  "translation": "O IP de origem da requisição não está na lista de IPs permitidos da integração.",
  "code": "EGW000007",
  "extra_fields": {}
}
```

Se você receber esse erro, confirme qual endereço a sua infraestrutura está de fato usando para sair (o IP público de saída pode não ser o do servidor, no caso de NAT, proxy ou balanceador) e compare com a lista que você nos enviou.

---

# Troca de chaves

URL: /documentation/seguros/primeiros_passos/seguros_troca_de_chaves

## Conferindo o formato da chave pública

A chave pública que esperamos receber é o arquivo gerado pelo **segundo** comando da seção anterior (`openssl ec ... -pubout`) — `jwtECDSASHA512.key.pub` no Unix e no Windows, `ec512-public.pem` no Mac OS. Ele está no formato **PEM**, em várias linhas, delimitado por `-----BEGIN PUBLIC KEY-----` e `-----END PUBLIC KEY-----`, como no exemplo abaixo — que serve apenas para conferir o formato e nunca deve ser cadastrado como a sua chave:

```
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAD8a66B1olkMDoeQM9imiOOCuq1Hq
LOq0bu6ry2GJJzDtjGws5u52SQikFFv0YSRSGpgAJN7cZiuXQkGooDxDvXsAev+2
iQn7HImrLN8YaNPqGMU28UFiuc2SSPf5QdHozEf6LRqq1bhg2oJirpLJAgKlse9M
hhsYr9sznWJDoLOJgjM=
-----END PUBLIC KEY-----
```

Confira o arquivo antes de nos enviar:

```bash
openssl ec -pubin -in jwtECDSASHA512.key.pub -text -noout
```

A resposta esperada termina em `ASN1 OID: secp521r1` e `NIST CURVE: P-521`. Se o comando responder um erro de leitura, o arquivo não é uma chave pública em PEM.

### Se a sua chave está no formato OpenSSH

O `ssh-keygen` do primeiro comando também grava um arquivo `.pub` ao lado da chave privada, mas ele está no formato **OpenSSH**: uma única linha começando pelo tipo da chave e terminando no comentário.

```
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAI... usuario@maquina
```

Esse arquivo corresponde ao mesmo par de chaves, mas **não é o formato aceito no cadastro da sua integração**. Você não precisa gerar um novo par: basta executar o comando `openssl ec ... -pubout` sobre a sua chave privada para obter a mesma chave pública em PEM.

## Envio da chave pública

Como parte da assinatura das requisições e das respostas, é necessário que você forneça a sua chave pública a nós e que retornemos uma chave pública para você — assim a leitura das mensagens pode ser feita nas duas pontas da comunicação. Além disso, fornecemos uma chave única do tipo UUID (`API-CLIENT-KEY`) que representa a sua integração via API dentro do nosso sistema.

No Insurance-as-a-Service o cadastro da chave pública ainda não está disponível pelo portal QI Tech. Envie a sua **chave pública em PEM** — o arquivo conferido na seção anterior — ao time de Integração da QI Tech por um **meio seguro** — entre em contato com [api@qitech.com.br](mailto:api@qitech.com.br) para alinhar o canal de envio. Em retorno, você receberá a sua **chave de integração** (`API-CLIENT-KEY`) e a **chave pública da QI Tech**.

:::danger Atenção!

Nunca compartilhe sua chave privada, ela é de uso exclusivo seu e o compartilhamento da mesma no lugar da chave pública compromete a segurança de suas requests. Além disso, não compartilhe sua chave pública QI Tech e chave de integração pois eles são seu meio de comunicação com nossas APIs.

:::

---

# Possíveis erros

URL: /documentation/seguros/primeiros_passos/teste_de_autenticacao/seguros_possiveis_erros

:::info Veja também
- [Teste de autenticação](./seguros_teste_de_autenticacao_v2)
- [Exemplo completo de autenticação](./seguros_teste_de_autenticacao_completo)
:::

## 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/seguros/primeiros_passos/teste_de_autenticacao/seguros_teste_de_autenticacao_completo

:::info Veja também
- [Teste de autenticação passo a passo](./seguros_teste_de_autenticacao_v2)
- [Possíveis erros](./seguros_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!

Requisições dos métodos `GET` e `DELETE` não possuem corpo. O hash md5 dessas requisições deve ser gerado sobre a **string vazia** (`""`), e o valor é sempre a constante abaixo:

```
d41d8cd98f00b204e9800998ecf8427e
```

Esse valor é **diferente** do utilizado no Lending-as-a-Service, que assina `GET` e `DELETE` com o md5 do objeto JSON vazio (`"{}"`), `99914b932bd37a50b983c5e7c90ae93b`. Se você já integra o Lending-as-a-Service, não reaproveite a constante: assinar um `GET` do Insurance-as-a-Service com ela retorna `401` com o código `EGW000006`.
:::

### Os dois erros `401` do header `AUTHORIZATION`

Uma falha no header `AUTHORIZATION` retorna `401` com um de dois códigos, e eles apontam para causas diferentes:

| Status | Código | Descrição |
|---|---|---|
| `401` | `EGW000004` | A assinatura da requisição não pôde ser verificada com a chave pública registrada da integração. |
| `401` | `EGW000006` | O digest do payload assinado não corresponde ao corpo da requisição. |

- `EGW000004` significa que o problema está no **par de chaves**: a assinatura não foi verificada com a chave pública registrada para a sua integração. Confira a [troca de chaves](/documentation/seguros/primeiros_passos/seguros_troca_de_chaves), inclusive o formato do arquivo enviado.
- `EGW000006` significa que a assinatura **foi verificada com sucesso** — a sua chave está correta — e que apenas o `md5` não corresponde ao corpo enviado. Não investigue o par de chaves: confira o hash, começando pela constante de `GET` e `DELETE` acima.

**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, timezone
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.now(timezone.utc).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.sandbox.insurance.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.sandbox.insurance.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.sandbox.insurance.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.sandbox.insurance.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.sandbox.insurance.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/seguros/primeiros_passos/teste_de_autenticacao/seguros_teste_de_autenticacao_v2

:::info Veja também
- [Exemplo completo de autenticação](./seguros_teste_de_autenticacao_completo)
- [Possíveis erros](./seguros_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.sandbox.insurance.qitech.app"
endpoint = "/test"
method = "POST"
request_body = {"name": "QI Tech"}
```
  

**PHP**

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

**Node.js**

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

**Java**

```java
private static final String base_url = "https://api.sandbox.insurance.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.sandbox.insurance.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!

Requisições dos métodos `GET` e `DELETE` não possuem corpo. O hash md5 dessas requisições deve ser gerado sobre a **string vazia** (`""`), e o valor é sempre a constante abaixo:

```
d41d8cd98f00b204e9800998ecf8427e
```

Esse valor é **diferente** do utilizado no Lending-as-a-Service, que assina `GET` e `DELETE` com o md5 do objeto JSON vazio (`"{}"`), `99914b932bd37a50b983c5e7c90ae93b`. Se você já integra o Lending-as-a-Service, não reaproveite a constante: assinar um `GET` do Insurance-as-a-Service com ela retorna `401` com o código `EGW000006`.
:::

### Os dois erros `401` do header `AUTHORIZATION`

Uma falha no header `AUTHORIZATION` retorna `401` com um de dois códigos, e eles apontam para causas diferentes:

| Status | Código | Descrição |
|---|---|---|
| `401` | `EGW000004` | A assinatura da requisição não pôde ser verificada com a chave pública registrada da integração. |
| `401` | `EGW000006` | O digest do payload assinado não corresponde ao corpo da requisição. |

- `EGW000004` significa que o problema está no **par de chaves**: a assinatura não foi verificada com a chave pública registrada para a sua integração. Confira a [troca de chaves](/documentation/seguros/primeiros_passos/seguros_troca_de_chaves), inclusive o formato do arquivo enviado.
- `EGW000006` significa que a assinatura **foi verificada com sucesso** — a sua chave está correta — e que apenas o `md5` não corresponde ao corpo enviado. Não investigue o par de chaves: confira o hash, começando pela constante de `GET` e `DELETE` acima.

### 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/seguros/primeiros_passos/teste_de_autenticacao/seguros_webhook_v2

:::info Veja também
- [Configurando Webhooks](/documentation/seguros/primeiros_passos/seguros_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/seguros/primeiros_passos/seguros_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();
}
```