MQTT was born as a publish/subscribe protocol — messages flow from many publishers to many subscribers with no expectation of a response. This model fits telemetry perfectly: sensors publish, dashboards subscribe, nobody waits for anyone. But when you need one client to ask another client a specific question and get one specific answer back, pub/sub alone falls short. You need request/response.
MQTT 3.1.1 forced engineers to invent their own request/response schemes on top of pub/sub — with topic-name conventions, payload correlation, and vendor-specific hacks. MQTT 5 solves this natively by defining four properties that together enable clean RPC-style communication over MQTT: Response Topic (0x08), Correlation Data (0x09), Request Response Information (0x19), and Response Information (0x1A).
This article explains the complete MQTT 5 request/response pattern: how the four properties work together, the two deployment models (peer-to-peer and service-discovery-based), the architectural patterns this unlocks (RPC, saga workflows, health checks), and how MQTT-based request/response compares to alternatives like HTTP and gRPC.
Table of Contents
What the MQTT 5 request/response pattern is in one paragraph
The MQTT 5 request/response pattern is the ability for one MQTT client to send a specific query to another and receive a specific answer back, using four PUBLISH-level properties. Response Topic (0x08) tells the responder where to publish its response. Correlation Data (0x09) identifies which request a response belongs to. Request Response Information (0x19) in CONNECT tells the Server the client wants response topic information. Response Information (0x1A) in CONNACK returns a Server-suggested topic prefix for the client to use for its response topics. Together, these four properties transform MQTT from pure pub/sub into a protocol capable of RPC, distributed workflows, health probes, and other request/response patterns while retaining pub/sub’s ability to handle thousands of concurrent asynchronous operations on a single connection.
The four building-block properties
Request/response in MQTT 5 uses four properties across CONNECT, CONNACK, and PUBLISH:
Response Topic (Property 0x08)
Appears in PUBLISH packets. UTF-8 string telling the responder where to publish its response.
| Aspect | Detail |
|---|---|
| Where used | PUBLISH packets (in the request) |
| Type | UTF-8 String |
| Max length | 65,535 bytes |
| Wildcards | Not allowed |
| Required for request/response | Yes |
Correlation Data (Property 0x09)
Appears in PUBLISH packets. Opaque binary bytes matching a response back to its request.
| Aspect | Detail |
|---|---|
| Where used | PUBLISH packets (in both request AND response) |
| Type | Binary Data |
| Max length | 65,535 bytes |
| Format | Application-chosen (UUID, sequence number, structured) |
| Required for multi-request | Yes (single in-flight can skip) |
Request Response Information (Property 0x19)
Appears in CONNECT packets. Boolean indicating the client wants Server-provided response topic information.
| Aspect | Detail |
|---|---|
| Where used | CONNECT packet |
| Type | Byte (boolean: 0 or 1) |
| Default | 0 (client doesn’t request response info) |
| Purpose | Client opt-in for service discovery pattern |
Response Information (Property 0x1A)
Appears in CONNACK packets. UTF-8 string providing a topic prefix or identifier the client should use for its response topics.
| Aspect | Detail |
|---|---|
| Where used | CONNACK packet |
| Type | UTF-8 String |
| Only sent if | Client sent Request Response Information = 1 in CONNECT |
| Content | Server-defined (topic prefix, unique identifier) |
| Purpose | Server tells client where to route response traffic |
The first two properties (Response Topic + Correlation Data) are the runtime mechanism — used on every request. The second two (Request Response Information + Response Information) are the discovery mechanism — used once at connection setup for centrally-managed deployments.
The two deployment models
MQTT 5 supports two distinct approaches to implementing request/response:
Model 1: Peer-to-peer
The client and responder agree on response topic conventions independently. The client picks its own response topic (typically embedding its Client Identifier) and includes it in every request. Simple and self-contained.
Model 2: Service Discovery
The Server centrally manages response topic conventions. Clients opt in via Request Response Information; the Server responds with a suggested topic prefix via Response Information. Suitable for multi-tenant systems, centrally-authorized deployments, or when clients shouldn’t hardcode topic conventions.
Both models use the same underlying Response Topic and Correlation Data mechanism. They differ only in how the client chooses its response topic.
Peer-to-peer pattern (direct response topics)
The simpler pattern. Client picks its own response topic.
The full flow
Setup (once at client startup):
Client → Server: SUBSCRIBE "device-007/responses" (QoS 1)
Server → Client: SUBACK
Per request:
Client → Server: PUBLISH
Topic: "services/calculator/add"
Properties:
Response Topic: "device-007/responses"
Correlation Data: <UUID_1>
Payload: {"a": 5, "b": 7}
Server → Service: PUBLISH (forwarded with same properties)
Service processes request, computes 5 + 7 = 12
Service → Server: PUBLISH
Topic: "device-007/responses" ← from Response Topic
Properties:
Correlation Data: <UUID_1> ← echoed from request
Payload: {"result": 12}
Server → Client: PUBLISH (forwarded)
Client matches UUID_1 to outstanding request, resolves the future
Advantages
- Simple — no discovery step, no Server-side coordination
- Portable — works with any MQTT 5 broker
- Predictable — response topics are stable and known
Disadvantages
- Client controls topic names — makes centralized topic-based authorization harder
- No enforcement — clients could use any response topic they want
- No tenant isolation — one tenant’s responses could accidentally cross into another tenant’s topic space (with poor topic design)
For simple deployments with a controlled set of clients, peer-to-peer is the right choice. Most public MQTT documentation demonstrates this pattern.
Service discovery pattern
The Server-managed pattern. The client asks the Server “where should my responses come to?” and the Server tells it.
The full flow
Client → Server: CONNECT
Client Identifier: "sensor-042"
Properties:
Request Response Information: 1 ← "I want response info"
Server → Client: CONNACK
Reason Code: 0x00 Success
Properties:
Response Information: "tenant-acme/responses/sensor-042"
← Server-assigned prefix
Client subscribes to the Server-assigned prefix (possibly with wildcards):
Client → Server: SUBSCRIBE "tenant-acme/responses/sensor-042/#"
Per request, client uses topics under the Server-assigned prefix:
Client → Server: PUBLISH
Topic: "services/calculator/add"
Properties:
Response Topic: "tenant-acme/responses/sensor-042/req-12345"
Correlation Data: <UUID_1>
Payload: {"a": 5, "b": 7}
What the Server provides in Response Information
The MQTT 5 spec deliberately leaves Response Information format flexible. Common Server-supplied patterns:
| Pattern | Example |
|---|---|
| Topic prefix | "tenant-acme/responses/sensor-042" |
| Unique identifier | "7d3b2a1e-5f6c-4c3d-a1b2-9e8f7d6c5b4a" |
| Structured path | "/tenants/acme/clients/sensor-042/rpc" |
| Simple identifier | "C7A2B1" (short ID mapped internally) |
The client is expected to use this value as-is (as a topic prefix) or resolve it somehow (looking up the mapping internally). The specific interpretation depends on the Server implementation.
Advantages
- Multi-tenant enforcement — Server assigns topic prefixes per tenant
- Authorization simplicity — topic-based auth follows Server-assigned patterns
- Central topic naming — Server controls the topic conventions
- Dynamic configuration — Server can rotate topic prefixes over time
Disadvantages
- Server-specific behavior — different Servers implement Response Information differently
- Extra complexity — discovery step adds moving parts
- Portability challenges — code depending on Server-provided Response Information format needs adjustment when moving between brokers
For multi-tenant SaaS platforms and centrally-managed enterprise deployments, service discovery makes sense. For simpler self-hosted setups, peer-to-peer is usually easier.
Architectural patterns unlocked
The MQTT 5 request/response mechanism enables several distinct architectural patterns:
Pattern 1: Classic RPC
One client calls a specific service and gets back a specific result:
Client → Service: PUBLISH "services/orders/create", request_data
Service → Client: PUBLISH on Response Topic, response_data
Use cases: microservices communication, backend integration, tool automation.
Pattern 2: Query-Response
Client requests information from a data provider:
Client → Data Store: PUBLISH "query/customer/lookup", {"customer_id": "C001"}
Data Store → Client: PUBLISH on Response Topic, {"name": "...", "balance": 1000}
Use cases: distributed databases, configuration retrieval, device state queries.
Pattern 3: Command-and-Acknowledgment
Client issues a command and waits for confirmation of execution:
Client → Device: PUBLISH "device/valve-42/close", {}
Device → Client: PUBLISH on Response Topic, {"status": "closed", "duration_ms": 250}
Use cases: industrial control, remote actuation, device management.
Pattern 4: Health Check / Liveness Probe
Monitor checks if a service is alive and responsive:
Monitor → Service: PUBLISH "healthcheck/service-xyz", {"deep": true}
Service → Monitor: PUBLISH on Response Topic, {"status": "healthy", "uptime_s": 12345}
Use cases: system monitoring, load balancer health checks, dependency validation.
Pattern 5: Fire-and-Forget with Optional Response
Publisher sends a message. Some processors respond (subscribed with response capability), others don’t (fire-and-forget subscribers):
Client → Multiple recipients: PUBLISH "event/order-placed", ...
With Response Topic
Responding subscribers: respond via Response Topic
Non-responding subscribers: process silently
Use cases: hybrid patterns where some receivers report and others just act.
Pattern 6: Broadcast Query
Client asks a question to many potential responders:
Client → Subscribers: PUBLISH "discovery/services/available", ...
With Response Topic
Each service that matches: PUBLISH response with its identifier
Client aggregates responses until timeout
Use cases: service discovery, device enumeration, capability querying.
Async design patterns
MQTT 5 request/response is inherently asynchronous. Clients typically model this with one of several patterns:
Pattern: Async/Await (Python-style)
python
async def call_service(service, method, params):
correlation_id = uuid.uuid4().bytes
future = asyncio.Future()
pending_requests[correlation_id] = future
await mqtt_client.publish(
topic=f"services/{service}/{method}",
payload=json.dumps(params),
response_topic="my-client/responses",
correlation_data=correlation_id,
qos=1
)
return await asyncio.wait_for(future, timeout=30)
The await cleanly suspends until the response arrives (or timeout fires).
Pattern: Promise/Then (JavaScript-style)
javascript
function callService(service, method, params) {
const correlationId = crypto.randomUUID();
return new Promise((resolve, reject) => {
pendingRequests.set(correlationId, { resolve, reject });
mqttClient.publish(
`services/${service}/${method}`,
JSON.stringify(params),
{
responseTopic: 'my-client/responses',
correlationData: correlationId,
qos: 1
}
);
setTimeout(() => {
if (pendingRequests.has(correlationId)) {
pendingRequests.delete(correlationId);
reject(new Error('Timeout'));
}
}, 30000);
});
}
Pattern: Callback (traditional)
call_service("calculator", "add", {"a": 5, "b": 7}, on_result_callback);
function on_result_callback(error, result) {
if (error) { handle_error(error); }
else { process_result(result); }
}
The MQTT layer delivers the response via the on_message handler, which looks up the matching callback by Correlation Data.
Pattern: Streaming/Observable
For responses that might arrive multiple times (streamed data):
client.subscribeToResponses(correlationId).forEach(response => {
processResponse(response);
});
The subscriber processes each response as it arrives until an end-of-stream marker or timeout.
Chained requests and saga workflows
MQTT 5’s request/response mechanism scales up to complex distributed workflows:
Chained requests
One request triggers another:
Client → Service A: PUBLISH "step-1"
Service A → Client: PUBLISH "response-1"
Client → Service B: PUBLISH "step-2" (using data from response-1)
Service B → Client: PUBLISH "response-2"
Client → Service C: PUBLISH "step-3" (using data from response-2)
Service C → Client: PUBLISH "response-3"
Each step is a standard request/response. The client orchestrates the chain.
Saga pattern (distributed workflow)
For workflows spanning multiple services with compensation on failure:
Client → Service A: reserve resource
Service A → Client: reserved (with compensation ID)
Client → Service B: process payment
Service B → Client: succeeded (or failed)
If failure:
Client → Service A: release resource (compensation)
Correlation Data can carry the saga ID linking all steps. This lets logging/monitoring systems trace the complete saga across services.
Multi-service parallel query
Client queries multiple services in parallel:
Client → Service A: PUBLISH request (Correlation Data = txn_id + "a")
Client → Service B: PUBLISH request (Correlation Data = txn_id + "b")
Client → Service C: PUBLISH request (Correlation Data = txn_id + "c")
All three services respond to the client's response topic.
Client matches each response by its part of the Correlation Data.
Client aggregates all responses once all three arrive.
This is fan-out/fan-in, natural in MQTT because there’s no HTTP-style per-request TCP connection overhead.
Failure recovery
Request/response over unreliable networks requires deliberate failure handling:
Timeout strategy
Always define request timeouts:
Short timeouts (< 1 second): interactive UI
Medium timeouts (1-30 seconds): standard RPC
Long timeouts (30+ seconds): batch operations, long-running processes
Without timeouts, pending request maps grow unboundedly on failed responses.
Late response handling
Responses arriving after their request has timed out should be discarded silently. The pending request entry has already been removed; the on_message handler won’t find a matching callback.
Best practice: log late responses at DEBUG level for observability but don’t act on them — the caller has already given up.
QoS considerations
| QoS | Effect on request/response |
|---|---|
| QoS 0 | Fastest, but requests or responses may be lost silently |
| QoS 1 | At-least-once — reliable but responses may be duplicated |
| QoS 2 | Exactly-once — guaranteed delivery, higher latency |
For most RPC-style patterns, QoS 1 is the right balance. Use QoS 2 only when duplicate processing is unacceptable.
Idempotency
With QoS 1 possibly duplicating messages, responders should handle duplicate requests idempotently. Correlation Data serves as a natural idempotency key:
Responder logic:
1. Extract Correlation Data from incoming request
2. Look up in recent-request cache (with TTL)
3. If found: return cached response (same as first time)
4. If not found: process request, cache result, return response
The cache TTL should exceed the client’s timeout — typically 60-120 seconds is reasonable.
Connection failure recovery
If the client loses its MQTT connection mid-request, several outcomes are possible:
- Request never sent — retry the request on reconnection
- Request sent, response lost — timeout fires, retry with same Correlation Data (idempotency prevents duplicates)
- Response arrived but not delivered — depends on QoS: QoS 0 lost, QoS 1+ redelivered with Session Expiry preserving state
Session Expiry Interval preserves in-flight state across reconnections when appropriate.
Multi-tenant considerations
For SaaS or multi-tenant deployments, request/response gets more nuanced:
Topic isolation
Different tenants must have isolated topic spaces so their responses don’t collide:
Tenant A responses: "tenants/a/responses/*"
Tenant B responses: "tenants/b/responses/*"
Enforced via topic-based authorization on the Server.
Cross-tenant requests
If a service serves multiple tenants, the request routing must include tenant context. Options:
- Include tenant ID in the request topic (
services/tenant-a/calculator/add) - Include tenant ID in Correlation Data (structured binary format)
- Include tenant ID in User Properties
- Include tenant ID in payload
Whichever approach, ensure the tenant identity is authenticated (via CIP Security or equivalent) — not just declared by the client.
Authorization complexity
Response topic authorization needs care:
Rule: client X can publish only to topics under "tenants/X/*"
Rule: client X can subscribe only to topics under "tenants/X/*"
Rule: services can publish to any tenant's response topics
Getting authorization rules right is often the hardest part of multi-tenant MQTT deployments.
When to use MQTT 5 request/response vs alternatives
Choose MQTT 5 request/response when:
- You’re already using MQTT for other purposes (unified protocol stack)
- Async patterns fit your use case
- Multiple concurrent requests must share one connection
- The Server needs to send unsolicited updates (native pub/sub)
- IoT / edge / constrained network context
- You need standard MQTT features (retained messages, QoS levels, session expiry) alongside request/response
Choose HTTP when:
- Synchronous, request-scoped semantics fit
- Web browsers / standard REST clients are common
- Load balancers and CDNs need to handle traffic
- Debugging via curl / Postman is important
- Rich HTTP semantics (caching, redirects, content negotiation) matter
Choose gRPC when:
- You need strong typing (protobuf schemas)
- Streaming patterns are common
- Multiplexing on HTTP/2 is beneficial
- Compile-time client/server contracts matter
- Backend-to-backend communication (not IoT devices)
Choose AMQP when:
- Enterprise messaging with complex routing (topics, queues, headers)
- Message durability guarantees are critical
- Existing enterprise messaging infrastructure (RabbitMQ, IBM MQ)
- Not primarily IoT or edge use cases
For most industrial IoT scenarios where devices, gateways, and cloud services communicate, MQTT 5 request/response is a strong choice — you get pub/sub for streaming telemetry AND request/response for command/query patterns over a single protocol.
Comparison with alternatives
| Aspect | MQTT 5 Request/Response | HTTP REST | gRPC | AMQP |
|---|---|---|---|---|
| Connection model | Persistent, long-lived | Per-request (or Keep-Alive) | Persistent (HTTP/2) | Persistent |
| Sync/Async | Async native | Sync + WebSocket for async | Async streaming | Async |
| Bidirectional | Yes native | Server push via SSE/WebSocket | Yes | Yes |
| Message correlation | Correlation Data property | URL + connection state | HTTP/2 stream ID | Correlation ID |
| Server discovery | Response Information | DNS + URL paths | DNS + service naming | Broker + exchange config |
| Schema definition | Application-defined | OpenAPI (external) | Protobuf (native) | Schema registry |
| Authentication | TLS + CIP Security / EST | HTTP Auth headers | TLS + JWT | TLS + SASL |
| Rate control | Flow Control | HTTP status codes | Backpressure | Prefetch |
| IoT device support | Excellent | Adequate | Limited | Rare |
| Cloud-native support | Growing | Universal | Standard | Enterprise |
Common design pitfalls
| Pitfall | What happens | How to avoid |
|---|---|---|
| Race condition on subscription | Request sent before subscription active — responses lost | Subscribe to response topic BEFORE publishing requests, wait for SUBACK |
| Per-request response topics | Topic explosion at Server | Use stable response topics + Correlation Data |
| Missing timeout | Pending request maps grow unboundedly | Always implement client-side timeouts |
| Non-idempotent responders | QoS 1 retries cause duplicate processing | Cache responses by Correlation Data |
| Confusing UUID string with bytes | Correlation Data mismatch | Treat Correlation Data as opaque binary bytes |
| Ignoring Response Topic absent | Responder always responds, even to fire-and-forget messages | Only respond when Response Topic is present |
| Response topic without auth | Anyone can subscribe to responses | Enforce topic-level authorization |
| Assuming Response Information format | Client hardcodes topic format | Treat Response Information as opaque Server-provided value |
Frequently asked questions
What is the MQTT 5 request/response pattern?
The MQTT 5 request/response pattern is a protocol-level mechanism for one client to send a specific request to another and receive a specific response back. It uses four PUBLISH-level properties: Response Topic (0x08) tells the responder where to send the answer; Correlation Data (0x09) matches responses to requests; Request Response Information (0x19) in CONNECT opts into service discovery; Response Information (0x1A) in CONNACK provides the Server’s suggested response topic prefix. Together they enable RPC-style patterns on top of MQTT’s pub/sub foundation.
How is MQTT 5 request/response different from MQTT 3.1.1?
MQTT 3.1.1 had no protocol-level request/response support. Engineers had to invent their own topic conventions and payload correlation schemes. This produced topic explosion, complex authorization rules, non-portable code, and DIY correlation logic. MQTT 5 moves correlation into standardized properties, letting request/response work identically across all MQTT 5 implementations without custom conventions.
What is Request Response Information in MQTT 5?
Request Response Information (Property 0x19) is a boolean property (byte value 0 or 1) that a client sends in CONNECT to tell the Server it wants response topic information. If set to 1, the Server may respond in CONNACK with Response Information (Property 0x1A) providing a Server-suggested topic prefix or identifier for the client to use for its response topics. This enables Server-managed response topic conventions for multi-tenant systems and centrally-managed deployments.
What is Response Information in MQTT 5?
Response Information (Property 0x1A) is a UTF-8 string that the Server sends in CONNACK when the client requested it via Request Response Information in CONNECT. The string content is Server-defined — typically a topic prefix like “tenant-acme/responses/client-042” or a unique identifier the client uses to construct response topics. This lets the Server centrally manage response topic conventions instead of clients hardcoding their own.
Do I need both Response Topic and Correlation Data for request/response?
For proper request/response with multiple concurrent requests, yes. Response Topic tells the responder where to publish the response. Correlation Data tells the requester which request the response belongs to. Without Correlation Data, the requester can’t distinguish among multiple in-flight responses on the same response topic. If you only ever have one request in flight at a time, you can skip Correlation Data — but this is rare in real systems.
What’s the difference between peer-to-peer and service discovery patterns?
Peer-to-peer request/response has the client choose its own response topic name (e.g., “client-id/responses”) — self-contained but topic conventions are client-controlled. Service Discovery has the client ask the Server via Request Response Information; the Server tells the client what topic prefix to use via Response Information. Service Discovery enables Server-side control over topic naming, useful for multi-tenant systems where the Server enforces topic isolation policies.
How does MQTT 5 request/response compare to HTTP REST?
MQTT 5 request/response is async-native, uses a persistent connection, supports thousands of concurrent requests on one connection, and integrates with pub/sub for server-initiated notifications. HTTP REST is sync-native, uses per-request or persistent connections, is universal across web infrastructure, and has strong tooling (curl, Postman, browsers). Choose MQTT for IoT/async patterns and unified protocol stacks; choose HTTP for web-style synchronous APIs and browser compatibility.
How do I handle timeouts in MQTT 5 request/response?
Implement client-side timeouts: track outstanding requests with their Correlation Data and start time, and remove entries after a timeout elapses. When a late response arrives, the pending request entry is gone, so the on_message handler silently discards the response. Common timeout values: 1-30 seconds for interactive RPC, longer for batch operations. Without timeouts, pending request maps grow unboundedly on network failures.
How do I subscribe to responses before publishing requests?
Always subscribe to your response topic BEFORE publishing any requests. Wait for the SUBACK confirming the subscription is active. Then publish requests. If you publish before subscribing, responses arriving between the publish and the subscription-active moment are lost. For long-lived clients, subscribe once at connection startup and reuse the same response topic for all requests — combined with Correlation Data for matching.
Can I use request/response with QoS 0?
Technically yes, but QoS 0 risks losing requests or responses silently. For any operational request/response pattern, use QoS 1 (at-least-once) which handles occasional network glitches by retrying. If duplicate processing would be unacceptable, either use QoS 2 (exactly-once, with latency cost) or implement idempotency in the responder using Correlation Data as an idempotency key. QoS 0 is rarely appropriate for request/response.
How do responders know if a PUBLISH is a request or fire-and-forget?
By the presence or absence of Response Topic. If a PUBLISH has Response Topic, treat it as a request and send a response. If not, treat it as fire-and-forget and process without responding. This lets the same service topic handle both patterns transparently — clients decide per-message whether they want a response by including or omitting Response Topic.
What’s the maximum size for Correlation Data?
Correlation Data is Binary Data with a 2-byte length prefix, giving a maximum of 65,535 bytes. In practice, Correlation Data is almost always much smaller — 16 bytes (UUID) or 8 bytes (sequence number) is typical. Large Correlation Data wastes bandwidth on every request AND every response. Use the smallest size that meets your correlation needs.
Should services always respond, even on errors?
Yes. When the responder can’t process the request (validation failure, authorization error, internal error), still publish a response with error indication in the payload. The requester is waiting for SOME response — silent failure just makes them wait until timeout, which is a bad user experience. Include status codes, error types, and human-readable messages in error responses to help requesters diagnose issues.
