Cuando crea integraciones que reciben webhooks de Zendesk, abre un punto final en su servidor que acepta solicitudes HTTP desde Internet. Sin la verificación adecuada, cualquiera podría enviar solicitudes falsas a ese punto final y potencialmente desencadenar acciones no deseadas en su sistema. Ahí es donde entra en juego la verificación de firmas.
La verificación de firmas de webhook de Zendesk le brinda una forma de probar criptográficamente que los webhooks entrantes realmente provienen de Zendesk y no fueron manipulados en tránsito. Esta guía lo guía a través de todo lo que necesita para implementarlo correctamente, con ejemplos de código funcional en cinco lenguajes de programación populares.
Si está buscando una guía más amplia sobre cómo configurar webhooks en Zendesk, nuestra guía de configuración de webhooks de mensajería de Zendesk cubre el proceso de configuración completo.

Qué es la verificación de firmas de webhook y por qué es importante
La verificación de firmas de webhook es un mecanismo de seguridad que le permite confirmar la autenticidad de las solicitudes de webhook entrantes. Cuando Zendesk envía un webhook a su punto final, incluye una firma criptográfica que solo Zendesk podría haber generado. Su servidor vuelve a calcular esa firma utilizando un secreto compartido y compara los resultados. Si coinciden, el webhook es genuino.
Sin esta verificación, su punto final es vulnerable a varios ataques:
- Suplantación (Spoofing): Cualquiera que descubra la URL de su webhook podría enviar solicitudes falsas pretendiendo ser Zendesk.
- Ataques de repetición (Replay attacks): Un atacante podría capturar un webhook legítimo y reenviarlo varias veces.
- Manipulación de la carga útil (Payload tampering): Los datos de la solicitud podrían modificarse en tránsito sin ser detectados.
Para las integraciones de producción que manejan datos confidenciales de tickets o que desencadenan flujos de trabajo automatizados, la verificación de firmas no es opcional. Es un control de seguridad fundamental que protege tanto su sistema como los datos de sus clientes.
En eesel AI, manejamos la seguridad de los webhooks automáticamente cuando conecta su cuenta de Zendesk. Nuestra plataforma verifica las firmas de forma transparente para que pueda concentrarse en la creación de automatizaciones en lugar de implementaciones criptográficas.
Cómo funcionan las firmas de webhook de Zendesk
Zendesk utiliza el algoritmo SHA256 HMAC (Código de autenticación de mensajes basado en hash) para generar firmas de webhook. El proceso combina el secreto de firma de su webhook con la carga útil de la solicitud y la marca de tiempo para crear una firma única para cada solicitud.
La fórmula se ve así:
base64(HMACSHA256(TIMESTAMP + BODY))
Esto es lo que sucede cuando Zendesk envía un webhook:
- Zendesk concatena la marca de tiempo y el cuerpo de la solicitud sin procesar en una sola cadena.
- Crea un hash HMAC-SHA256 utilizando el secreto de firma de su webhook como clave.
- El hash se codifica en Base64 para producir la firma final.
- Zendesk envía el webhook con dos encabezados críticos:
X-Zendesk-Webhook-Signature: la firma generadaX-Zendesk-Webhook-Signature-Timestamp: la marca de tiempo utilizada en la firma
Cada solicitud de webhook de Zendesk incluye estos encabezados estándar:
x-zendesk-account-id: 123456
x-zendesk-webhook-id: 01F1KRFQ6BG29CNWFR60NK5FNY
x-zendesk-webhook-invocation-id: 8350205582
x-zendesk-webhook-signature: EiqWE3SXTPQpPulBV6OSuuGziIishZNc1VwNZYqZrHU=
x-zendesk-webhook-signature-timestamp: 2021-03-25T05:09:27Z
En su servidor, extrae estos encabezados, vuelve a calcular la firma utilizando su secreto de firma almacenado y compara los resultados. Si las firmas coinciden, puede confiar en que el webhook provino de Zendesk y que la carga útil no ha sido modificada.
Recuperando su secreto de firma de webhook
Antes de que pueda verificar las firmas, necesita el secreto de firma para su webhook. Cada webhook en Zendesk tiene su propio secreto único que se genera cuando se crea el webhook.
Encontrando su secreto en el Centro de administración
- Navegue al Centro de administración de Zendesk (Centro de administración > Aplicaciones e integraciones > Webhooks)
- Seleccione el webhook que desea verificar
- En la página de detalles del webhook, busque el campo de secreto de firma
- Haga clic en "Revelar secreto" para mostrar el valor

Trate este secreto como cualquier otra credencial. No lo guarde en el código, no lo exponga en aplicaciones del lado del cliente y restrinja el acceso al mismo dentro de su equipo.
Recuperando a través de la API
También puede obtener el secreto de firma mediante programación utilizando la API Mostrar secreto de firma de webhook:
GET /api/v2/webhooks/{webhook_id}/signing_secret
Secreto de prueba estático para el desarrollo
Cuando esté probando webhooks antes de crearlos en Zendesk, necesitará un secreto de firma estático, ya que los secretos reales solo se generan después de la creación del webhook. Utilice este secreto de prueba durante el desarrollo:
dGhpc19zZWNyZXRfaXNfZm9yX3Rlc3Rpbmdfb25seQ==
Una vez que se crea su webhook, cambie al secreto de firma real. Los webhooks de prueba y los webhooks en vivo utilizan diferentes secretos, por lo que su código de verificación debe manejar el secreto correcto para cada entorno.

Guía de implementación paso a paso
La implementación de la verificación de firmas implica cuatro pasos clave. Analicemos cada uno de ellos.
Paso 1: Capturar el cuerpo de la solicitud sin procesar
La firma se calcula sobre el cuerpo de la solicitud sin procesar como una cadena, no sobre JSON analizado o datos de formulario. Si su marco de trabajo analiza el cuerpo antes de que pueda acceder a él, la verificación de la firma fallará porque los bytes sin procesar se han transformado.
La mayoría de los marcos de trabajo web proporcionan middleware u opciones de configuración para capturar el cuerpo sin procesar antes del análisis. Por lo general, necesita almacenar el cuerpo sin procesar en una propiedad como req.rawBody para que esté disponible para el cálculo de la firma.
Error común: el middleware de análisis del cuerpo (como express.json() de Express) a menudo se ejecuta antes que el controlador de su ruta. Si el cuerpo se analiza en un objeto JavaScript antes de capturar la cadena sin procesar, no puede recuperar los bytes originales para la verificación de la firma. Configure su middleware para capturar el cuerpo sin procesar primero.
Paso 2: Extraer los encabezados de firma
Extraiga los dos encabezados relacionados con la firma de la solicitud entrante:
X-Zendesk-Webhook-Signature: la firma para verificarX-Zendesk-Webhook-Signature-Timestamp: la marca de tiempo utilizada en el cálculo de la firma
Tenga en cuenta que algunos marcos de trabajo transforman los nombres de los encabezados. En Ruby on Rails, por ejemplo, el encabezado X-Zendesk-Webhook-Signature se convierte en HTTP_X_ZENDESK_WEBHOOK_SIGNATURE en el entorno de la solicitud.
Paso 3: Calcular la firma esperada
Concatene la marca de tiempo y el cuerpo sin procesar, luego cree un hash HMAC-SHA256 utilizando su secreto de firma:
- Cree una cadena:
timestamp + body(primero la marca de tiempo, luego el cuerpo sin procesar) - Genere HMAC-SHA256 utilizando su secreto de firma como clave
- Codifique el hash resultante en Base64
Esta firma calculada debe coincidir con la que Zendesk envió en el encabezado X-Zendesk-Webhook-Signature.
Paso 4: Comparar firmas de forma segura
Utilice una función de comparación de tiempo constante para comparar las firmas. La comparación de cadenas regular (== o ===) puede filtrar información sobre la firma a través del análisis de tiempo, lo que teóricamente podría ayudar a un atacante a falsificar firmas válidas.
La mayoría de los lenguajes proporcionan una función de comparación de tiempo constante:
- Node.js:
crypto.timingSafeEqual() - Python:
hmac.compare_digest() - PHP:
hash_equals() - Ruby:
ActiveSupport::SecurityUtils.secure_compare() - C#: No hay comparación de tiempo constante incorporada, pero
CryptographicOperations.FixedTimeEquals()en .NET Core
Si las firmas coinciden, procese el webhook. Si no coinciden, devuelva una respuesta 401 No autorizado y registre el fallo para su investigación.
Ejemplos de código en lenguajes populares
Aquí hay implementaciones completas y funcionales para los lenguajes de desarrollo web más comunes.
Node.js/Express
const express = require('express');
const crypto = require('crypto');
const SIGNING_SECRET = 'your_webhook_signing_secret_here';
const PORT = 3000;
const app = express();
// Middleware to capture raw body
function storeRawBody(req, res, buf) {
if (buf && buf.length) {
req.rawBody = buf.toString('utf8');
}
}
app.use(express.json({ verify: storeRawBody }));
app.use(express.urlencoded({ verify: storeRawBody, extended: true }));
function isValidSignature(signature, body, timestamp) {
const hmac = crypto.createHmac('sha256', SIGNING_SECRET);
const sig = hmac.update(timestamp + body).digest('base64');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(sig)
);
}
app.post('/webhook', (req, res) => {
const signature = req.headers['x-zendesk-webhook-signature'];
const timestamp = req.headers['x-zendesk-webhook-signature-timestamp'];
const body = req.rawBody;
if (!isValidSignature(signature, body, timestamp)) {
console.log('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}
// Process the verified webhook
console.log('Webhook verified, processing...');
res.status(200).send('OK');
});
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Python (Flask)
from flask import Flask, request, abort
import hmac
import hashlib
import base64
app = Flask(__name__)
SIGNING_SECRET = b'your_webhook_signing_secret_here'
@app.route('/webhook', methods=['POST'])
def handle_webhook():
# Get raw body
raw_body = request.get_data()
# Extract headers
signature = request.headers.get('X-Zendesk-Webhook-Signature', '')
timestamp = request.headers.get('X-Zendesk-Webhook-Signature-Timestamp', '')
# Calculate expected signature
signed_payload = (timestamp + raw_body.decode('utf-8')).encode('utf-8')
expected_signature = base64.b64encode(
hmac.new(SIGNING_SECRET, signed_payload, hashlib.sha256).digest()
).decode('utf-8')
# Verify signature
if not hmac.compare_digest(expected_signature, signature):
abort(401)
# Process verified webhook
return '', 200
if __name__ == '__main__':
app.run(port=3000)
PHP
<?php
define('SIGNING_SECRET', 'your_webhook_signing_secret_here');
function verify_webhook($body, $signature, $timestamp) {
// Concatenate timestamp and body
$signed_payload = $timestamp . $body;
// Calculate HMAC (binary output)
$calculated_hmac = base64_encode(
hash_hmac('sha256', $signed_payload, SIGNING_SECRET, true)
);
// Constant-time comparison
return hash_equals($signature, $calculated_hmac);
}
// Handle webhook request
$signature = $_SERVER['HTTP_X_ZENDESK_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_ZENDESK_WEBHOOK_SIGNATURE_TIMESTAMP'] ?? '';
$body = file_get_contents('php://input');
if (!verify_webhook($body, $signature, $timestamp)) {
http_response_code(401);
exit('Unauthorized');
}
// Process verified webhook
http_response_code(200);
echo 'OK';
Ruby on Rails
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
SIGNING_SECRET = ENV['ZENDESK_WEBHOOK_SECRET']
def zendesk
signature = request.headers['HTTP_X_ZENDESK_WEBHOOK_SIGNATURE']
timestamp = request.headers['HTTP_X_ZENDESK_WEBHOOK_SIGNATURE_TIMESTAMP']
body = request.body.read
# Calculate signature
signed_payload = timestamp + body
expected_signature = Base64.strict_encode64(
OpenSSL::HMAC.digest('SHA256', SIGNING_SECRET, signed_payload)
)
# Verify
unless ActiveSupport::SecurityUtils.secure_compare(expected_signature, signature)
head :unauthorized
return
end
# Process webhook
head :ok
end
end
C#
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("webhook")]
public class WebhookController : ControllerBase
{
private const string SigningSecret = "your_webhook_signing_secret_here";
[HttpPost]
public IActionResult HandleWebhook()
{
string signature = Request.Headers["X-Zendesk-Webhook-Signature"];
string timestamp = Request.Headers["X-Zendesk-Webhook-Signature-Timestamp"];
// Read raw body
using var reader = new StreamReader(Request.Body);
string body = reader.ReadToEnd();
// Calculate signature
string signedPayload = timestamp + body;
byte[] keyBytes = Encoding.UTF8.GetBytes(SigningSecret);
byte[] payloadBytes = Encoding.UTF8.GetBytes(signedPayload);
using var hmac = new HMACSHA256(keyBytes);
byte[] hash = hmac.ComputeHash(payloadBytes);
string expectedSignature = Convert.ToBase64String(hash);
// Compare (case-insensitive for compatibility)
if (!signature.Equals(expectedSignature, StringComparison.OrdinalIgnoreCase))
{
return Unauthorized();
}
return Ok();
}
}
Problemas comunes y resolución de problemas
Incluso con el código correcto, la verificación de la firma puede fallar por razones sutiles. Estos son los problemas más comunes que encuentran los desarrolladores.
Espaciado y formato JSON
Uno de los problemas más frustrantes involucra el formato JSON. La firma se calcula sobre los bytes exactos que Zendesk envía, incluido el espacio en blanco. Si su marco de trabajo reformatea el JSON (agregando o eliminando espacios), la firma no coincidirá.
Un desarrollador en la comunidad de Zendesk descubrió esto por las malas:
La solución es siempre verificar la firma contra el cuerpo de la solicitud sin procesar antes de que se produzca cualquier análisis o transformación.
Diferencias entre webhook de prueba y webhook en vivo
Otro problema común involucra las diferencias entre la función de webhook de prueba de Zendesk y las invocaciones de webhook en vivo. El formato de la carga útil puede variar ligeramente entre los dos, lo que hace que las firmas se validen en las pruebas pero fallen en la producción.
Siempre pruebe con invocaciones de webhook reales de eventos reales de Zendesk antes de implementar en producción.
Codificación de caracteres
Asegúrese de que tanto la marca de tiempo como el cuerpo se manejen como cadenas UTF-8 al concatenar para el cálculo de la firma. Las discrepancias de codificación entre su servidor y la carga útil de Zendesk provocarán fallos de verificación.
Validación de la marca de tiempo
Considere agregar la validación de la marca de tiempo para evitar ataques de repetición. Compruebe que la marca de tiempo en el encabezado esté dentro de una ventana razonable (por ejemplo, 5 minutos) de la hora actual. Las marcas de tiempo antiguas podrían indicar un ataque de repetición.
Cuándo regenerar secretos
Si sospecha que su secreto de firma ha sido comprometido, regenérelo inmediatamente a través del Centro de administración de Zendesk. Después de la regeneración, actualice su servidor con el nuevo secreto. Puede haber una breve ventana en la que los webhooks en vuelo utilicen el secreto anterior, así que considere admitir ambos durante la transición.
Probando su verificación de webhook
Antes de implementar en producción, pruebe a fondo su implementación de verificación de firma.
Usando el secreto de prueba estático
Durante el desarrollo, utilice el secreto de prueba estático de Zendesk (dGhpc19zZWNyZXRfaXNfZm9yX3Rlc3Rpbmdfb25seQ==) para verificar que su lógica de implementación sea correcta. Esto le permite probar sin crear un webhook en vivo.
Probando con la función de prueba de Zendesk
Cuando cree o edite un webhook en Zendesk, utilice el botón "Webhook de prueba" para enviar cargas útiles de prueba. Verifique que su punto final acepte estas solicitudes y valide la firma correctamente.
Pruebas en vivo
Cree un disparador real que invoque su webhook en un evento específico (como la creación de un ticket). Realice esa acción en Zendesk y confirme que su punto final recibe y verifica el webhook. Compruebe los registros de su servidor para detectar cualquier discrepancia en la firma.
Registro y depuración
Registre la siguiente información durante el desarrollo para ayudar a depurar los fallos:
- Cuerpo de la solicitud sin procesar (antes del análisis)
- Encabezado de firma recibido
- Firma calculada
- Encabezado de marca de tiempo
Nunca registre el secreto de firma en sí. Compare las firmas recibidas y calculadas carácter por carácter para identificar dónde divergen.
Asegure sus integraciones de Zendesk con eesel AI
La implementación correcta de la verificación de firmas de webhook requiere una atención cuidadosa a los detalles criptográficos, el manejo del cuerpo específico del marco de trabajo y los casos extremos en torno al formato JSON. Para los equipos que crean integraciones complejas, esta complejidad puede ralentizar el desarrollo e introducir riesgos de seguridad.
En eesel AI, hemos incorporado la seguridad de webhook directamente en nuestra integración de Zendesk. Cuando conecta su cuenta de Zendesk a nuestra plataforma, manejamos la verificación de firmas automáticamente. Obtiene los beneficios de seguridad sin escribir y mantener el código de verificación.

Nuestro Agente de IA para Zendesk va más allá, proporcionando una resolución autónoma de tickets mientras maneja toda la seguridad de webhook en segundo plano. Si está creando automatizaciones basadas en webhook y desea centrarse en la lógica empresarial en lugar de las implementaciones criptográficas, podemos ayudarle a simplificar su integración.
Preguntas Frecuentes
Compartir esta entrada

Article by
Stevia Putri
Stevia Putri is a marketing generalist at eesel AI, where she helps turn powerful AI tools into stories that resonate. She’s driven by curiosity, clarity, and the human side of technology.



