Assinatura de Requisições
Como segurança adicional, você pode usar assinaturas em todas as solicitações para e da nossa API. O uso de assinaturas é opcional. No entanto, se uma assinatura for enviada, ela deve ser válida.
Ao enviar solicitações para nossa API, você gera uma assinatura que envia com seus dados. Ao receber, o webhook de entrada contém a assinatura e todos os campos que você precisa para criar a assinatura em sua aplicação, a fim de verificar a correspondência das duas assinaturas e, assim, a precisão do webhook.
Com esta assinatura, pode-se garantir que
- a solicitação realmente vem da fonte correta,
- a solicitação não foi alterada durante o trânsito,
- as solicitações não sejam interceptadas e repetidas posteriormente.
A assinatura deve conter obrigatoriamente alguns dados, como o timestamp, a URL de destino, um chamado Nonce e os dados enviados.
Estrutura da Assinatura
Para criar uma assinatura, são necessários os seguintes dados:
| Valor | Descrição |
|---|---|
| Nonce | Uma string aleatória, composta por 32 caracteres alfanuméricos. Deve ser única para cada solicitação. |
| Timestamp | O momento da solicitação como Unix Timestamp, não deve ter mais de 30 segundos |
| Método HTTP | O método de solicitação HTTP, por exemplo, POST ou GET |
| URL de Destino | A URL completa para a qual a solicitação será enviada |
| Conteúdo da Solicitação | Hash MD5 do conteúdo da solicitação |
Para gerar a assinatura, é utilizado o algoritmo SHA256 HMAC com a chave de assinatura da sua conta. Pode encontrar a chave no seu login, na área Desenvolvedores, nas configurações da API.
Estrutura da sequência de caracteres a ser assinada
A sequência de caracteres a ser assinada deve ser construída da seguinte forma:
STRING_TO_SIGN =
carimbo de data/hora + \n +
Nonce + \n +
Método HTTP + \n +
URL de destino + \n +
ContentBodyMD5
Se você enviar, por exemplo, esta requisição:
POST https://gateway.seven.io/api/sms
{"to": "49170123456789", "text": "Olá, mundo! :-)", "from": "seven"}
A sequência de caracteres a ser assinada seria construída da seguinte forma:
STRING_TO_SIGN =
1634641200
fpPRhAd1s8GXacfR39mWqKPynmmXfJnc
POST
https://gateway.seven.io/api/sms
62dd06ffb3101dc2456517b177b744ae
| Valor | Explicação |
|---|---|
| 1634641200 | O timestamp atual no momento do envio da requisição, aqui 19.10.2021 às 13:00:00 |
| fpPRhAd1s8GXacfR39mWqKPynmmXfJnc | Uma string gerada aleatoriamente com 32 caracteres |
| POST | O método HTTP utilizado |
| https://gateway.seven.io/api/sms | A URL de destino |
| 62dd06ffb3101dc2456517b177b744ae | MD5( {"to": "49170123456789", "text": "Olá, mundo! :-)", "from": "seven"} ) |
Opcional: vinculação do X-Account-Id
Se você usar o cabeçalho X-Account-Id para agir em nome de uma subconta, anexe o ID da subconta como linha adicional à string a ser assinada. Sem essa vinculação, um atacante com acesso man-in-the-middle poderia redirecionar o cabeçalho para outra de suas subcontas sem quebrar a assinatura.
STRING_TO_SIGN =
Timestamp + \n +
Nonce + \n +
Método HTTP + \n +
URL de destino + \n +
ContentBodyMD5 + \n +
X-Account-Id # somente quando o cabeçalho está definido
Quando X-Account-Id não é enviado, a string termina em ContentBodyMD5 como mostrado acima - clientes existentes não precisam alterar nada.
Criação da assinatura
Para criar a assinatura, utilize o algoritmo SHA256 HMAC em conjunto com o chave de assinatura do seu account.
printf "${STRING_TO_SIGN}" | openssl dgst -sha256 -hmac "${SIGNING_SECRET}"
Implementações semelhantes estão disponíveis na maioria das linguagens de programação comuns, como por exemplo em PHP:
$signature = hash_hmac('sha256', $STRING_TO_SIGN, $SIGNING_SECRET);
Envio da Assinatura
A assinatura, o nonce e o timestamp devem ser enviados nos cabeçalhos HTTP da solicitação.
- Name
X-Signature- Type
- string
- Description
A assinatura gerada por você
- Name
X-Timestamp- Type
- integer
- Description
O timestamp no qual a assinatura foi criada
- Name
X-Nonce- Type
- string
- Description
O nonce criado por você
Validar os webhooks recebidos
Os webhooks que enviamos ao seu servidor são assinados pelo mesmo procedimento. Aplique do seu lado as mesmas regras que aplicamos às requisições recebidas pela API:
- Reconstrua a sequência a ser assinada a partir da requisição recebida (
X-Timestamp,X-Nonce, o método HTTP, a URL completa do seu endpoint e o hash MD5 do corpo da requisição sem alterações) e compare o resultado com o cabeçalhoX-Signature, idealmente com uma comparação de tempo constante. - Rejeite a requisição se o valor de
X-Timestamptiver mais de 30 segundos. - Mantenha os valores de
X-Nonceem um cache de curta duração e rejeite um nonce já visto (proteção contra repetição). Uma duração de cache pouco acima da sua janela de tempo é suficiente.
Os retries têm assinatura própria
Cada tentativa de entrega é assinada separadamente. O envio inicial, cada retry automático e um reenvio manual pelo depurador de Webhooks têm seu próprio X-Timestamp, seu próprio X-Nonce e uma X-Signature correspondente. Um retry que chega horas após o evento continua, portanto, com um timestamp atual e um nonce que nunca foi usado antes.
Por isso você não precisa ampliar a sua janela de validação para os retries: mantenha os 30 segundos e a verificação do nonce exatamente como descrito acima. O plano de retry está documentado na página Webhooks.
Não use o campo webhook_timestamp do payload para essa verificação. Ele indica o momento do evento e, em um retry, difere propositalmente do cabeçalho X-Timestamp. Valide sempre o valor do cabeçalho.
Exemplos
Script Bash com curl e openssl
#!/bin/bash
# Os seus dados API
API_KEY="SUA_CHAVE_API"
SIGNING_SECRET="SUA_CHAVE_DE_SINAIS"
# Criar um nonce aleatório
NONCE=$(openssl rand -hex 32)
# carimbo de data/hora
TIMESTAMP=$(date +%s)
# Daten des Requests
HTTP_VERB="POST"
URL="https://gateway.seven.io/api/sms"
CONTENT_BODY="{ \"to\": \"0170123456789\", \"text\": \"Olá, mundo! :-)\", \"from\": \"seven.io\" }"
# Criar assinatura
CONTENT_BODY_MD5=$(printf "${CONTENT_BODY}" | md5sum | awk '{print $1}')
STRING_TO_SIGN=$(printf "${TIMESTAMP}
${NONCE}
${HTTP_VERB}
${URL}
${CONTENT_BODY_MD5}")
SIGNATURE=$(printf "${STRING_TO_SIGN}" | openssl dgst -sha256 -hmac "${SIGNING_SECRET}" | sed 's/^.*= //')
# Enviar pedido
curl -X $HTTP_VERB $URL \
-H "X-Api-Key: $API_KEY" \
-H "X-Nonce: $NONCE" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
-H "Content-type: application/json" \
-H "Accept: application/json" \
-d "${CONTENT_BODY}"
Script PHP
# Os seus dados API
$key = 'SUA_CHAVE_API';
$secret = 'SUA_CHAVE_DE_SINAIS';
# Criar um nonce aleatório
$nonce = bin2hex(random_bytes(16));
# carimbo de data/hora
$timestamp = time();
# Dados da solicitação
$http_verb = 'POST';
$content_body = json_encode([
'to' => '49170123456789',
'text' => 'Olá, mundo! :-)',
'from' => 'seven.io',
]);
$url = 'https://gateway.seven.io/api/sms';
# Criar assinatura
$StringToSign = [$timestamp, $nonce, $http_verb, $url, md5($content_body)];
$StringToSign = implode(PHP_EOL, $StringToSign);
$hash = hash_hmac('sha256', $StringToSign, $secret);
# Definir cabeçalho
$headers = [
'X-Signature: ' . $hash,
'X-Api-Key: ' . $key,
'X-Timestamp:' . $timestamp,
'X-Nonce:' . $nonce,
'Content-type: application/json',
'Accept: application/json'
];
# Enviar pedido
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $content_body);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
echo $result;