Firmado de Solicitudes
Como seguridad adicional, puede utilizar firmas en todas las solicitudes hacia y desde nuestra API. El uso de firmas es opcional. Sin embargo, si se envía una firma, esta debe ser válida.
Al enviar solicitudes a nuestra API, genere una firma que envíe junto con sus datos. Al recibir, el webhook entrante contiene la firma y todos los campos que necesita para crear la firma en su aplicación, para verificar la coincidencia de ambas firmas y, por lo tanto, la exactitud del webhook.
Mediante esta firma se puede asegurar que
- la solicitud realmente proviene de la fuente correcta,
- la solicitud no ha sido alterada en tránsito,
- las solicitudes no sean interceptadas y repetidas más tarde.
La firma contiene necesariamente algunos datos como, entre otros, la marca de tiempo, la URL de destino, un llamado Nonce y los datos enviados.
Estructura de la Firma
Para crear una firma se necesitan los siguientes datos:
| Valor | Descripción |
|---|---|
| Nonce | Una cadena aleatoria, compuesta por 32 caracteres alfanuméricos. Debe ser única para cada solicitud. |
| Marca de tiempo | El momento de la solicitud como un Timestamp Unix, no debe ser mayor a 30 segundos de antigüedad |
| Método HTTP | El método de solicitud HTTP, por ejemplo, POST o GET |
| URL de destino | La URL completa a la que se envía la solicitud |
| Contenido de la solicitud | Hash MD5 del contenido de la solicitud |
Para la generación de la firma se utiliza el algoritmo SHA256 HMAC con la clave de firma de su cuenta. Puede encontrar la clave en su inicio de sesión en la sección Desarrollador en la configuración de la API.
Estructura de la cadena de caracteres a firmar
La cadena a firmar debe estructurarse de la siguiente manera:
STRING_TO_SIGN =
Marca de tiempo + \n +
Nonce + \n +
Método HTTP + \n +
URL de destino + \n +
ContentBodyMD5
Por ejemplo, si envía esta solicitud:
POST https://gateway.seven.io/api/sms
{"to": "49170123456789", "text": "Hola mundo! :-)", "from": "seven"}
La cadena a firmar se estructuraría de la siguiente manera:
STRING_TO_SIGN =
1634641200
fpPRhAd1s8GXacfR39mWqKPynmmXfJnc
POST
https://gateway.seven.io/api/sms
62dd06ffb3101dc2456517b177b744ae
| Valor | Explicación |
|---|---|
| 1634641200 | La marca de tiempo actual en el momento de enviar la solicitud, aquí el 19.10.2021 a las 13:00:00 |
| fpPRhAd1s8GXacfR39mWqKPynmmXfJnc | Una cadena generada aleatoriamente con 32 caracteres |
| POST | El método HTTP utilizado |
| https://gateway.seven.io/api/sms | La URL de destino |
| 62dd06ffb3101dc2456517b177b744ae | MD5( {"to": "49170123456789", "text": "Hola mundo! :-)", "from": "seven"} ) |
Opcional: vinculación de X-Account-Id
Si utiliza el encabezado X-Account-Id para actuar en nombre de una subcuenta, añada el ID de la subcuenta como línea adicional a la cadena a firmar. Sin esta vinculación, un atacante con acceso man-in-the-middle podría redirigir el encabezado a otra de sus subcuentas sin romper la firma.
STRING_TO_SIGN =
Timestamp + \n +
Nonce + \n +
Método HTTP + \n +
URL destino + \n +
ContentBodyMD5 + \n +
X-Account-Id # solo cuando el encabezado está definido
Cuando X-Account-Id no se envía, la cadena termina en ContentBodyMD5 como se muestra arriba - los clientes existentes no necesitan cambiar nada.
Creación de la firma
Para crear la firma, utilice el algoritmo SHA256 HMAC junto con la clave de firma de su cuenta.
printf "${STRING_TO_SIGN}" | openssl dgst -sha256 -hmac "${SIGNING_SECRET}"
Implementaciones similares están disponibles en la mayoría de los lenguajes de programación comunes, por ejemplo, en PHP:
$signature = hash_hmac('sha256', $STRING_TO_SIGN, $SIGNING_SECRET);
Envío de la firma
La firma, el nonce y la marca de tiempo deben enviarse en los encabezados HTTP de la solicitud.
- Name
X-Signature- Type
- string
- Description
La firma generada por usted
- Name
X-Timestamp- Type
- integer
- Description
La marca de tiempo en la que se creó la firma
- Name
X-Nonce- Type
- string
- Description
El nonce creado por usted
Validar los webhooks que recibe
Los webhooks que enviamos a su servidor se firman con el mismo procedimiento. Aplique en su lado las mismas reglas que nosotros aplicamos a las solicitudes entrantes de la API:
- Reconstruya la cadena a firmar a partir de la solicitud recibida (
X-Timestamp,X-Nonce, el método HTTP, la URL completa de su endpoint y el hash MD5 del cuerpo de la solicitud sin modificar) y compare el resultado con el encabezadoX-Signature, idealmente con una comparación de tiempo constante. - Rechace la solicitud si el valor de
X-Timestamptiene más de 30 segundos de antigüedad. - Guarde los valores de
X-Nonceen una caché de corta duración y rechace un nonce que ya haya visto (protección contra repeticiones). Basta con una duración de caché ligeramente superior a su ventana de tiempo.
Los reintentos llevan su propia firma
Cada intento de entrega se firma por separado. El envío inicial, cada reintento automático y un reenvío manual desde el depurador de Webhooks llevan su propio X-Timestamp, su propio X-Nonce y una X-Signature correspondiente. Un reintento que llega horas después del evento sigue llevando, por tanto, una marca de tiempo actual y un nonce que nunca se había utilizado antes.
Por eso no necesita ampliar su ventana de validación para los reintentos: mantenga los 30 segundos y la comprobación del nonce exactamente como se describe arriba. El plan de reintentos está documentado en la página Webhooks.
No utilice el campo webhook_timestamp del payload para esta comprobación. Se refiere al momento del evento y, en un reintento, difiere deliberadamente del encabezado X-Timestamp. Valide siempre el valor del encabezado.
Ejemplos
Script de Bash con curl y openssl
#!/bin/bash
# Sus datos API
API_KEY="SU_CLAVE_API"
SIGNING_SECRET="SU_CLAVE_DE_FIRMA"
# Crear un nonce aleatorio
NONCE=$(openssl rand -hex 32)
# marca de tiempo
TIMESTAMP=$(date +%s)
# Datos de la solicitud
HTTP_VERB="POST"
URL="https://gateway.seven.io/api/sms"
CONTENT_BODY="{ \"to\": \"0170123456789\", \"text\": \"Hola mundo! :-)\", \"from\": \"seven.io\" }"
# Crear firma
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 solicitud
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 de PHP
# Sus datos API
$key = 'SU_CLAVE_API';
$secret = 'SU_CLAVE_DE_FIRMA';
# Crear un nonce aleatorio
$nonce = bin2hex(random_bytes(16));
# marca de tiempo
$timestamp = time();
# Datos de la solicitud
$http_verb = 'POST';
$content_body = json_encode([
'to' => '49170123456789',
'text' => 'Hola mundo! :-)',
'from' => 'seven.io',
]);
$url = 'https://gateway.seven.io/api/sms';
# Crear firma
$StringToSign = [$timestamp, $nonce, $http_verb, $url, md5($content_body)];
$StringToSign = implode(PHP_EOL, $StringToSign);
$hash = hash_hmac('sha256', $StringToSign, $secret);
# Establecer encabezado
$headers = [
'X-Signature: ' . $hash,
'X-Api-Key: ' . $key,
'X-Timestamp:' . $timestamp,
'X-Nonce:' . $nonce,
'Content-type: application/json',
'Accept: application/json'
];
# Enviar solicitud
$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;