REST API bridge
Publish MQTT messages via HTTP REST endpoints - ideal for serverless functions, webhooks, and backends without native MQTT support.
The REST API bridge extension lets you publish MQTT messages using standard HTTP requests. Use it for:
| Use case | Examples or notes |
|---|---|
| Serverless functions | AWS Lambda, Vercel, Cloudflare Workers |
| No-code platforms | Bubble.io, Make, Zapier |
| Backend services | Without native MQTT libraries |
| Webhooks | Trigger MQTT messages from external events |
This is a paid extension. See pricing below for plan details.
How it works
Your HTTP request is converted to an MQTT message and delivered to all subscribers.
Getting started
Enable the extension
- Go to Dashboard → Extensions
- Find REST API Bridge
- Click Enable or Subscribe
- Choose your plan based on expected request volume
Get your API key
After enabling:
- Go to Extensions → REST API Bridge → Settings
- Your API Key is displayed (click to copy)
- Note the Endpoint URL for your region
Keep your API key secret! Anyone with the key can publish to your MQTT topics.
Make your first request
Publish a message using curl:
curl -X POST https://rest.cloudsignal.io/api/v1/publish \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key-here" \
-d '{
"topic": "tasks/task-3/progress",
"payload": "{\"step\": 4, \"of\": 10, \"status\": \"running\"}",
"qos": 1
}'API reference
Endpoint
POST https://rest.cloudsignal.io/api/v1/publishHeaders
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your REST API Bridge API key |
Content-Type | Yes | Must be application/json |
Request body
{
"topic": "string", // Required: MQTT topic to publish to
"payload": "string", // Required: Message content (usually JSON string)
"qos": 0, // Optional: 0, 1, or 2 (default: 0)
"retain": false // Optional: Retain message (default: false)
}Response
Success (200)
{
"success": true,
"message_id": "msg_abc123"
}Error (4xx/5xx)
{
"success": false,
"error": "Invalid API key",
"code": "UNAUTHORIZED"
}Error codes
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Invalid or missing API key |
FORBIDDEN | 403 | API key doesn't have permission for this topic |
INVALID_REQUEST | 400 | Malformed JSON or missing required fields |
TOPIC_INVALID | 400 | Topic contains invalid characters |
PAYLOAD_TOO_LARGE | 413 | Payload exceeds maximum size (256KB) |
RATE_LIMITED | 429 | Too many requests, slow down |
QUOTA_EXCEEDED | 429 | Monthly request quota exceeded |
Code examples
// Node.js / Browser
async function publishMessage(topic, payload) {
const response = await fetch('https://rest.cloudsignal.io/api/v1/publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.CLOUDSIGNAL_API_KEY
},
body: JSON.stringify({
topic,
payload: JSON.stringify(payload),
qos: 1
})
});
if (!response.ok) {
throw new Error(`Publish failed: ${response.status}`);
}
return response.json();
}
// Usage
await publishMessage('agents/agent-01/inbox', { task: 'summarize-thread' });import os
import json
import requests
def publish_message(topic: str, payload: dict, qos: int = 0):
response = requests.post(
'https://rest.cloudsignal.io/api/v1/publish',
headers={
'Content-Type': 'application/json',
'X-API-Key': os.environ['CLOUDSIGNAL_API_KEY']
},
json={
'topic': topic,
'payload': json.dumps(payload),
'qos': qos
}
)
response.raise_for_status()
return response.json()
# Usage
publish_message('presence/room-9', {'online': 12, 'typing': 2})package main
import (
"bytes"
"encoding/json"
"net/http"
"os"
)
type PublishRequest struct {
Topic string `json:"topic"`
Payload string `json:"payload"`
QoS int `json:"qos"`
}
func publishMessage(topic string, payload interface{}) error {
payloadBytes, _ := json.Marshal(payload)
reqBody, _ := json.Marshal(PublishRequest{
Topic: topic,
Payload: string(payloadBytes),
QoS: 1,
})
req, _ := http.NewRequest("POST",
"https://rest.cloudsignal.io/api/v1/publish",
bytes.NewBuffer(reqBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", os.Getenv("CLOUDSIGNAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}<?php
function publishMessage(string $topic, array $payload, int $qos = 0): array {
$ch = curl_init('https://rest.cloudsignal.io/api/v1/publish');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . getenv('CLOUDSIGNAL_API_KEY')
],
CURLOPT_POSTFIELDS => json_encode([
'topic' => $topic,
'payload' => json_encode($payload),
'qos' => $qos
])
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Usage
publishMessage('alerts/system', ['level' => 'warning', 'message' => 'CPU high']);# Basic publish
curl -X POST https://rest.cloudsignal.io/api/v1/publish \
-H "Content-Type: application/json" \
-H "X-API-Key: $CLOUDSIGNAL_API_KEY" \
-d '{
"topic": "notifications/user-123",
"payload": "{\"title\": \"New message\", \"body\": \"Hello!\"}",
"qos": 1
}'
# With retain flag
curl -X POST https://rest.cloudsignal.io/api/v1/publish \
-H "Content-Type: application/json" \
-H "X-API-Key: $CLOUDSIGNAL_API_KEY" \
-d '{
"topic": "agents/agent-42/state",
"payload": "{\"status\": \"idle\"}",
"retain": true
}'Use cases
Serverless functions
Publish from AWS Lambda, Vercel Edge Functions, or Cloudflare Workers:
// Vercel Edge Function
export default async function handler(req) {
const { userId, message } = await req.json();
await fetch('https://rest.cloudsignal.io/api/v1/publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.CLOUDSIGNAL_API_KEY
},
body: JSON.stringify({
topic: `users/${userId}/notifications`,
payload: JSON.stringify({ message, timestamp: Date.now() })
})
});
return new Response('Notification sent');
}No-code platforms (Bubble.io)
Use Bubble's API Connector plugin:
- Add a new API:
CloudSignal REST API - Authentication: None (we use headers)
- Add shared header:
X-API-Key= your key - Create call: POST to
https://rest.cloudsignal.io/api/v1/publish - Body type: JSON with
topicandpayloadfields
Webhooks (Stripe, GitHub, and others)
Forward webhook events to MQTT subscribers:
// Express.js webhook handler
app.post('/webhooks/stripe', async (req, res) => {
const event = req.body;
// Forward to MQTT subscribers
await fetch('https://rest.cloudsignal.io/api/v1/publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.CLOUDSIGNAL_API_KEY
},
body: JSON.stringify({
topic: `webhooks/stripe/${event.type}`,
payload: JSON.stringify(event)
})
});
res.sendStatus(200);
});Dashboard features
Overview tab
View your usage at a glance:
| Metric | Description |
|---|---|
| Requests today / this month | Track against your quota |
| Success rate | Percentage of successful publishes |
| Average latency | Time to process requests |
Settings tab
Manage your API key:
| Action | Description |
|---|---|
| View / copy API key | Click to copy |
| Regenerate key | Invalidates the old key immediately |
| Endpoint URL | Your region's REST endpoint |
Logs tab
Debug failed requests:
| Field | Description |
|---|---|
| Timestamp | When the request was made |
| Topic | Which topic was targeted |
| Status | Success or error code |
| Latency | Processing time in milliseconds |
Filter logs by status, topic, or date range.
Topic permissions
The REST API Bridge uses your organization's ACL rules. Your API key can only publish to topics that match your configured ACL patterns.
By default, the REST API user has publish access to all topics (#). You can restrict this in Dashboard → ACL Rules.
Rate limits
| Plan | Requests/minute | Requests/month |
|---|---|---|
| Starter | 60 | 10,000 |
| Professional | 300 | 100,000 |
| Scale | 1,000 | 1,000,000 |
Exceeding rate limits returns a 429 status code. Implement exponential backoff:
async function publishWithRetry(topic, payload, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch('https://rest.cloudsignal.io/api/v1/publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.CLOUDSIGNAL_API_KEY
},
body: JSON.stringify({ topic, payload: JSON.stringify(payload) })
});
if (response.status !== 429) return response;
// Exponential backoff: 1s, 2s, 4s
await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
}
throw new Error('Rate limited after retries');
}Pricing
| Plan | Monthly Price | Requests/month | Features |
|---|---|---|---|
| Starter | $9 | 10,000 | Basic publishing, 7-day logs |
| Professional | $29 | 100,000 | Higher rate limits, 30-day logs |
| Scale | $99 | 1,000,000 | Priority support, 90-day logs |
Enable the extension from Dashboard → Extensions → REST API Bridge.
Security best practices
| Practice | Why |
|---|---|
| Never expose your API key in client-side code | Use only from server-side |
| Use environment variables | Don't hardcode keys |
| Restrict ACL rules | Limit which topics the REST API can publish to |
| Monitor logs | Watch for unusual patterns or failed requests |
| Regenerate keys periodically | Rotate keys as part of security hygiene |
Troubleshooting
"Invalid API key" error
- Verify the key is copied correctly (no extra spaces)
- Check the key hasn't been regenerated
- Ensure the extension is still enabled
"Topic invalid" error
Topics cannot contain:
- Null characters
+or#(these are wildcard characters for subscriptions only)
Messages not reaching subscribers
- Check the topic matches subscriber patterns
- Verify ACL rules allow the REST API user to publish
- Check subscriber is connected and subscribed
High latency
- Use the endpoint closest to your servers
- Reduce payload size if possible
- Check your network connectivity
Next steps
- Create MQTT user - Set up clients to receive messages
- ACL rules - Control topic access
- Monitoring - Track message delivery