SunoVoiceAI

Developers — API & webhooks

Setting up webhooks

Receive real-time events like completed calls at your own endpoint, and verify their signatures securely.

Webhooks push events from SunoVoice AI to your own systems the moment they happen — so you can log calls in your CRM, trigger a Slack message, or kick off an automation. Instead of polling the API, you give us a URL and we POST to it when something occurs.

Add a webhook endpoint

  1. Open the Webhooks tab.
  2. Click to add an endpoint and enter the URL that should receive events (it must be publicly reachable over HTTPS).
  3. Choose which events to subscribe to.
  4. Save. We return a signing secret (it starts with whsec_) once — copy and store it now. You'll use it to verify that incoming requests really came from us.

You can send a test event from the dashboard to confirm your endpoint receives and responds correctly.

Events

EventWhen it fires
call.completedA call finishes — includes its details and summary

We add new event types over time; subscribe only to the ones you need.

The request we send

We send an HTTP POST with a JSON body shaped like this:

{
  "event": "call.completed",
  "timestamp": "2026-06-12T09:30:00Z",
  "data": { }
}

Headers on every delivery:

HeaderValue
X-SunoVoice-EventThe event type, e.g. call.completed
X-SunoVoice-SignatureBase64 HMAC-SHA256 signature of the request

Your endpoint should respond with a 2x status quickly (within a few seconds). Do any heavy processing asynchronously.

Verifying the signature

Always verify the signature before trusting a webhook, so attackers can't forge events. The signature is:

base64( HMAC_SHA256( secret, timestamp + "." + raw_request_body ) )

where timestamp is the value from the JSON body and raw_request_body is the exact bytes you received (don't re-serialize the JSON — use the raw body). Compare your computed value to the X-SunoVoice-Signature header.

Python

import hmac, hashlib, base64, json

def verify(secret: str, raw_body: bytes, signature_header: str) -> bool:
    timestamp = json.loads(raw_body)["timestamp"]
    payload = timestamp.encode() + b"." + raw_body
    expected = base64.b64encode(
        hmac.new(secret.encode(), payload, hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(expected, signature_header)

Node.js

const crypto = require("crypto");

function verify(secret, rawBody, signatureHeader) {
  const timestamp = JSON.parse(rawBody).timestamp;
  const payload = Buffer.concat([Buffer.from(timestamp + "."), Buffer.from(rawBody)]);
  const expected = crypto.createHmac("sha256", secret).update(payload).digest("base64");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

Reject any request whose signature doesn't match.

Retries & reliability

If your endpoint is down or returns a non-2xx status, we retry with backoff so a brief outage doesn't lose events. Every delivery attempt is logged on the Webhooks tab, where you can see whether each one succeeded and its response status.

Because retries can happen, design your handler to be idempotent — processing the same event twice should be safe.

Best practices

  • Verify every signature before acting on a payload.
  • Respond fast (2xx), then process asynchronously.
  • Be idempotent to handle retries safely.
  • Keep your signing secret private, just like an API key.
  • Use the delivery log to debug failures.

Next step

Manage your plan and payments: Plans & billing.

Still stuck? Contact our team and we'll help you out.