ratchetphp/Ratchet · error · Exception

Invalid Topic, must be a string

Error message

Invalid Topic, must be a string

What it means

Thrown when a WAMP message's element at index 1 — the Topic/URI used by PREFIX, CALL, SUBSCRIBE, UNSUBSCRIBE and PUBLISH frames — is neither a string nor a number. The check `isset($json[1]) && !(is_string($json[1]) || is_numeric($json[1]))` guards the topic before dispatch, because `getUri()` and prefix lookup require a string topic URI.

Solutions

  1. Ensure element 1 of every client-to-server WAMP frame (SUBSCRIBE/UNSUBSCRIBE/PUBLISH topic, CALL proc URI, PREFIX shortname) is a plain string, e.g. [5, "http://example.com/topic"].
  2. Fix client-side frame construction order: WAMP v1 expects [type, topic, ...payload], not [type, options, topic].
  3. Add a client-side serializer that validates the frame shape before sending: assert(typeof msg[1] === 'string').
  4. On the server, catch the exception in onError and log the connection to identify which client is sending malformed frames.

Example fix

// before (client)
ws.send(JSON.stringify([5, { uri: 'kittens' }]));
// after
ws.send(JSON.stringify([5, 'kittens']));
Defensive patterns

Strategy: validation

Validate before calling

$frame = json_decode($msg, true);
if (isset($frame[1]) && !is_string($frame[1]) && !is_numeric($frame[1])) {
    // reject: element 1 (topic/proc URI) must be a string or number
}

Type guard

function hasValidWampTopic(array $frame): bool {
    return !isset($frame[1]) || is_string($frame[1]) || is_numeric($frame[1]);
}

Try / catch

try {
    $server->onMessage($conn, $msg);
} catch (\Ratchet\Wamp\Exception $e) {
    error_log("Bad topic from client: " . $msg);
    $conn->close();
}

Prevention

When it happens

Trigger: A client sends a frame like [5, {"uri": "topic"}] or [5, ["topic"]] or [5, null] where the second element is an array, object or null instead of a string topic URI. Note null at index 1 actually fails isset() and would not trigger; triggers specifically for arrays, booleans (after json_decode arrays) and nested structures.

Common situations: Client serialization bugs that nest the topic inside an object; WAMP v2-style publish options objects placed in the wrong position, e.g. [7, {exclude: ...}, topic] instead of legacy [7, topic]; hand-written protocol implementations misordering the frame elements.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of ratchetphp/Ratchet@e621c6c40b (2026-09-16). Data as JSON: /api/errors/324f360ae1a5ab3a. Report an issue: GitHub.

Appendix: source

Thrown at src/Ratchet/Wamp/ServerProtocol.php:97

    /**
     * {@inheritdoc}
     * @throws \Ratchet\Wamp\Exception
     * @throws \Ratchet\Wamp\JsonException
     */
    public function onMessage(ConnectionInterface $from, $msg) {
        $from = $this->connections[$from];

        if (null === ($json = @json_decode($msg, true))) {
            throw new JsonException;
        }

        if (!is_array($json) || $json !== array_values($json)) {
            throw new Exception("Invalid WAMP message format");
        }

        if (isset($json[1]) && !(is_string($json[1]) || is_numeric($json[1]))) {
            throw new Exception('Invalid Topic, must be a string');
        }

        switch ($json[0]) {
            case static::MSG_PREFIX:
                $from->WAMP->prefixes[$json[1]] = $json[2];
            break;

            case static::MSG_CALL:
                array_shift($json);
                $callID  = array_shift($json);
                $procURI = array_shift($json);

                if (count($json) == 1 && is_array($json[0])) {
                    $json = $json[0];
                }

                $this->_decorating->onCall($from, $callID, $from->getUri($procURI), $json);
            break;

View on GitHub (pinned to e621c6c40b)