Slack slash command for music: 3 s ack, 5 replies in 30 min

A Slack command must be acknowledged in 3 s; response_url then allows 5 replies in 30 min. Start the Sume music job in webhook mode and reply when it lands.

4 min readSume
All posts

For a Slack slash command that makes a music track, answer Slack inside 3 seconds, start the Sume job in webhook mode, and reply later through the response_url. Slack's docs say that URL can be used up to 5 times within 30 minutes of the payload. A Sume Music Router job is accepted immediately, so the two clocks fit if you spend replies on purpose.

The two clocks

Slack's page on interactions states two rules. Your app must send an HTTP 200 within 3 seconds of receiving the payload, or the user sees an error. Delayed responses go to the response_url, which accepts up to 5 responses within 30 minutes.

Slack and Sume timing (Slack docs and Sume docs, read 2026-10-05)
ClockLimitWhat it means for a music command
Slack acknowledgement200 OK within 3 secondsNever wait for the track inside the command handler
Slack response_url5 responses, 30 minutesOne working reply, one result, three spare for retries or errors
Sume sync waitAt most 30 seconds, then pollDo not use sync for a song; use webhook mode
Sume webhook deliveryUp to 10 attempts, 30 s apart, 10 s timeoutReturn 2xx fast, then post to Slack

The flow

The command handler does three things, then returns 200 with no body or a short working line. It derives an Idempotency-Key from the command, starts the job, and stores job_id next to the response_url. The Sume webhook receiver, which is a different route, looks up the response_url by job_id and posts the result.

The Music Router charges a fixed $0.125 per generation, and POST /v1/music-router/generate has no duration field, so put the length in the prompt text, for example a 30-second track.

import hashlib, json, os, urllib.request

JOBS = {}  # job_id -> response_url (use a real store)

def start_track(prompt, response_url, receiver_url):
    key = "slack-" + hashlib.sha256((response_url + prompt).encode()).hexdigest()[:32]
    body = {"prompt": prompt, "mode": "webhook", "webhook_url": receiver_url}
    req = urllib.request.Request(
        "https://api.sume.com/v1/music-router/generate",
        data=json.dumps(body).encode(),
        headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
                 "Content-Type": "application/json", "Idempotency-Key": key})
    with urllib.request.urlopen(req, timeout=10) as r:
        job_id = json.load(r)["request_id"]
    JOBS[job_id] = response_url
    return job_id

def on_sume_event(event):
    url = JOBS.get(event["job_id"])
    if not url:
        return
    arts = (event.get("payload") or {}).get("artifacts") or []
    audio = next((a["url"] for a in arts if a.get("type") == "audio"), None)
    text = audio or "The track failed. Nothing was created."
    req = urllib.request.Request(url, data=json.dumps({"text": text}).encode(),
                                 headers={"Content-Type": "application/json"})
    urllib.request.urlopen(req, timeout=10).read()

Spend the five replies deliberately

Reply 1 is the working message. Reply 2 is the result. Keep the remaining three for a failure notice, a retry that the user asks for, and one spare. If the job takes longer than 30 minutes from the original command, the response_url is dead and you need a normal Slack message to a channel instead.

Limits

Verify Sume's signature on the receiver before you touch the store: the header is x-sume-webhook-signature: sume-v1=<hex> over <timestamp>.<raw_body>. Do not put a non-empty negative_prompt in the request, because Music Router returns 400 for it. The response_url is a credential for that channel, so keep it out of logs.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume