Webhook Security Verification
Suno API uses the HMAC-SHA256 algorithm to generate cryptographic signatures, ensuring the integrity, authenticity, and non-repudiation of webhook callbacks sent to your server.
You can generate and view your unique webhookHmacKey in the API Keys & Webhooks Dashboard. The key is used to verify that incoming webhook requests originate from official Suno API servers. Keep this key secure and never commit it to public code repositories.
Signature Generation Formula
When dispatching a webhook callback to your specified callBackUrl, the signature is generated as follows:
Formula Variables:
taskId: The unique task ID from the callback body (data.task_id).timestamp: The Unix timestamp in seconds from theX-Webhook-Timestamprequest header.webhookHmacKey: Your private Webhook secret key obtained from your dashboard.
Webhook Header Description
When the Webhook HMAC Key is configured, all callback requests sent to your server include the following HTTP headers:
| Header Field | Type | Required | Description |
|---|---|---|---|
| X-Webhook-Timestamp | Integer | Yes | Unix timestamp (in seconds) when the callback request was sent by Suno API. |
| X-Webhook-Signature | String | Yes | Signature generated using HMAC-SHA256 algorithm with Base64 encoding. |
Step-by-Step Verification Process
Read Header Fields
Extract X-Webhook-Timestamp and X-Webhook-Signature from the incoming HTTP request headers. Optionally verify that the timestamp is within 300 seconds (5 minutes) of current time to prevent replay attacks.
Generate Expected Signature
Extract task_id from the callback JSON body. Concatenate taskId + "." + timestamp, calculate the HMAC-SHA256 digest using your webhookHmacKey, and Base64-encode the result.
Constant-Time Signature Comparison
Compare the expected signature with X-Webhook-Signature using a constant-time comparison algorithm (such as Node.js crypto.timingSafeEqual or Python hmac.compare_digest) to prevent timing side-channel attacks.
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Set your Webhook HMAC Key from /dashboard/keys
const WEBHOOK_HMAC_KEY = process.env.WEBHOOK_HMAC_KEY;
function generateSignature(taskId, timestampSeconds, secret) {
// 1. Concatenate taskId and timestamp
const dataToSign = `${taskId}.${timestampSeconds}`;
// 2. Calculate HMAC-SHA256 signature
const hmac = crypto.createHmac('sha256', secret);
hmac.update(dataToSign);
// 3. Base64 encode
return hmac.digest('base64');
}
function verifySignature(taskId, timestampSeconds, receivedSignature, secret) {
const expectedSignature = generateSignature(taskId, timestampSeconds, secret);
// Constant-time length check
if (expectedSignature.length !== receivedSignature.length) {
return false;
}
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);
}
app.post('/webhook-callback', (req, res) => {
// 1. Read header fields
const timestamp = req.headers['x-webhook-timestamp'];
const receivedSignature = req.headers['x-webhook-signature'];
if (!timestamp || !receivedSignature) {
return res.status(401).json({ error: 'Missing signature headers' });
}
// 2. Extract task_id from payload
const taskId = req.body?.data?.task_id || req.body?.taskId;
if (!taskId) {
return res.status(400).json({ error: 'Missing task_id' });
}
// 3. Verify HMAC signature
const isValid = verifySignature(taskId, timestamp, receivedSignature, WEBHOOK_HMAC_KEY);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Signature verified, process callback data safely
const { code, msg, data } = req.body;
console.log('Received legitimate webhook request:', {
taskId: data.task_id,
status: code,
callbackType: data.callbackType
});
return res.status(200).json({ status: 'received' });
});
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});POST /your-webhook-endpoint HTTP/1.1
Host: your-server.com
Content-Type: application/json
X-Webhook-Timestamp: 1769670760
X-Webhook-Signature: KxDlpbbq0GDOKqm0+FuJpJWTzY8baHSjhEt4kwElqQI=
{
"code": 200,
"msg": "Success",
"data": {
"task_id": "ee9c2715375b7837f8bb51d641ff5863",
"callbackType": "task_completed",
"data": [
{
"id": "audio_12345678",
"audio_url": "https://cdn.sunoapi.top/audio/track_01.mp3",
"image_url": "https://cdn.sunoapi.top/covers/cover_01.jpg",
"title": "Neon Horizon",
"duration": 184.5
}
]
}
}Manage Your Webhook Key
Retrieve your current HMAC key or regenerate a new one anytime in the Developer Console.
Go to /dashboard/keys