Skip to main content
Qwen-Audio-TTS

WebSocket connection guide

Access the Qwen-Audio-TTS real-time speech synthesis service through a WebSocket connection. Covers service endpoints, request headers, and the client-server interaction workflow.

Rate limits: Model calls are subject to rate limits. When a limit is exceeded, the server returns the error Requests rate limit exceeded, please try again later. Reduce your request rate or concurrency and retry. For the rate limits of each model, see Rate limiting.

Service endpoints

The following WebSocket URL is for Singapore (ap-southeast-1). For another region, replace ap-southeast-1 with the region code and use an API key from that region. See Regions and endpoints. wss://ap-southeast-1.maas.qwencloudapi.com/api-ws/v1/inference

Request headers

Include the following headers in the request:
ParameterTypeRequiredDescription
AuthorizationstringYesFormat: Bearer $DASHSCOPE_API_KEY.
user-agentstringNoClient identifier for request tracking.
X-DashScope-WorkSpacestringNoQwenCloud workspace ID.
X-DashScope-DataInspectionstringNoEnables data inspection. Default: enable. Leave unset unless required.
Authorization is verified during the WebSocket handshake. If the API key is invalid or missing, the handshake fails with an HTTP 401/403 error.

Interaction workflow

image
For details about client-side and server-side events, see Client events and Server-side events. Client-server interaction sequence:
  1. Establish a connection: Create a WebSocket connection to the server.
  2. Start a task: Send a run-task event.
  3. Wait for confirmation: Receive a task-started event from the server before proceeding.
  4. Send text for synthesis: Send one or more continue-task events with the text to synthesize, in sequence. The server returns a result-generated event with an audio stream after each complete sentence. For text length limits, see the text field in the continue-task event.
Send multiple continue-task events with text segments in order. The server splits text into sentences:
  • Complete sentences are synthesized and returned as audio immediately.
  • Incomplete sentences are buffered until complete.
A finish-task event forces synthesis of all buffered content.
  1. Receive audio: Read the audio stream from the binary channel.
  2. End the task: After sending all text, send a finish-task event and continue receiving audio. This step is mandatory — skipping it may produce incomplete audio.
  3. Receive task completion: A task-finished event from the server confirms the task is complete.
  4. Close the connection: Disconnect the WebSocket.
Reuse the WebSocket connection across tasks instead of creating a new one for each.
All events in a synthesis task (run-task, continue-task, finish-task) must share the same task_id. Generate a new task_id (such as a UUID) for each task. Mismatched values cause audio corruption or task failure.

Code examples

These examples use generic WebSocket libraries to send protocol events directly, with qwen-audio-3.1-tts-flash and the longanhuan_v3.1 voice. Obtain an API key and set the DASHSCOPE_API_KEY environment variable. When switching regions, change both the endpoint and the API key.
  • Java
  • Python
  • Go
  • C#
  • PHP
  • Node.js
We recommend using the Java DashScope SDK for development. See Qwen-Audio-TTS Java SDK.The following is a Java WebSocket direct connection example. Import these dependencies before running:
  • Java-WebSocket
  • jackson-databind
Use Maven or Gradle to manage dependencies:
pom.xml
<dependencies>
    <!-- WebSocket Client -->
    <dependency>
        <groupId>org.java-websocket</groupId>
        <artifactId>Java-WebSocket</artifactId>
        <version>1.5.3</version>
    </dependency>

    <!-- JSON Processing -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.0</version>
    </dependency>
</dependencies>
Java code:
import com.fasterxml.jackson.databind.ObjectMapper;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.*;

public class TTSWebSocketClient extends WebSocketClient {
    private final String taskId = UUID.randomUUID().toString();
    private final String outputFile = "output_" + System.currentTimeMillis() + ".mp3";
    private boolean taskFinished = false;

    public TTSWebSocketClient(URI serverUri, Map<String, String> headers) {
        super(serverUri, headers);
    }

    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        System.out.println("Connection established");

        // Send the run-task event
        // If enable_ssml is set to true, only one continue-task event can be sent; otherwise the error "Text request limit violated, expected 1." will be returned
        String runTaskCommand = "{ \"header\": { \"action\": \"run-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"task_group\": \"audio\", \"task\": \"tts\", \"function\": \"SpeechSynthesizer\", \"model\": \"qwen-audio-3.1-tts-flash\", \"parameters\": { \"text_type\": \"PlainText\", \"voice\": \"longanhuan_v3.1\", \"format\": \"mp3\", \"sample_rate\": 22050, \"volume\": 50, \"rate\": 1, \"pitch\": 1, \"enable_ssml\": false }, \"input\": {} }}";
        send(runTaskCommand);
    }

    @Override
    public void onMessage(String message) {
        System.out.println("Received message from server: " + message);
        try {
            // Parse JSON message
            Map<String, Object> messageMap = new ObjectMapper().readValue(message, Map.class);

            if (messageMap.containsKey("header")) {
                Map<String, Object> header = (Map<String, Object>) messageMap.get("header");

                if (header.containsKey("event")) {
                    String event = (String) header.get("event");

                    if ("task-started".equals(event)) {
                        System.out.println("Received task-started event from server");

                        List<String> texts = Arrays.asList(
                                "Before my bed, moonlight shines bright,I suspect it\'s frost upon the ground.",
                                "I raise my eyes to gaze at the bright moon,then bow my head, thinking of home."
                        );

                        for (String text : texts) {
                            // Send the continue-task event
                            sendContinueTask(text);
                        }

                        // Send the finish-task event
                        sendFinishTask();
                    } else if ("task-finished".equals(event)) {
                        System.out.println("Received task-finished event from server");
                        taskFinished = true;
                        closeConnection();
                    } else if ("task-failed".equals(event)) {
                        System.out.println("Task failed: " + message);
                        closeConnection();
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("Exception occurred: " + e.getMessage());
        }
    }

    @Override
    public void onMessage(ByteBuffer message) {
        System.out.println("Received binary audio data of size: " + message.remaining());

        try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
            byte[] buffer = new byte[message.remaining()];
            message.get(buffer);
            fos.write(buffer);
            System.out.println("Audio data written to local file " + outputFile);
        } catch (IOException e) {
            System.err.println("Failed to write audio data to local file: " + e.getMessage());
        }
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        System.out.println("Connection closed: " + reason + " (" + code + ")");
    }

    @Override
    public void onError(Exception ex) {
        System.err.println("Error: " + ex.getMessage());
        ex.printStackTrace();
    }

    private void sendContinueTask(String text) {
        String command = "{ \"header\": { \"action\": \"continue-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": { \"text\": \"" + text + "\" } }}";
        send(command);
    }

    private void sendFinishTask() {
        String command = "{ \"header\": { \"action\": \"finish-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": {} }}";
        send(command);
    }

    private void closeConnection() {
        if (!isClosed()) {
            close();
        }
    }

    public static void main(String[] args) {
        try {
            // If you have not configured the environment variable, replace the next line with your QwenCloud API Key: String apiKey = "sk-xxx"
            String apiKey = System.getenv("DASHSCOPE_API_KEY");
            if (apiKey == null || apiKey.isEmpty()) {
                System.err.println("Please set the DASHSCOPE_API_KEY environment variable");
                return;
            }

            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "bearer " + apiKey);
            // Singapore (ap-southeast-1). For another region, replace ap-southeast-1 in the URL.
            // API keys are region-specific. See https://docs.qwencloud.com/developer-guides/getting-started/regions
            TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://ap-southeast-1.maas.qwencloudapi.com/api-ws/v1/inference"), headers);

            client.connect();

            while (!client.isClosed() && !client.taskFinished) {
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            System.err.println("Failed to connect to WebSocket service: " + e.getMessage());
            e.printStackTrace();
        }
    }
}