Maranduva API
Maranduva is a real-time messaging platform built around a simple model: publish an event to a channel via HTTP, and any connected WebSocket client subscribed to that channel receives it instantly.
Two endpoints, that's it. A POST /push to publish, and a WebSocket URL to subscribe. Messages are delivered in real time.
Here's the full flow:
- Your server (or any HTTP client) publishes an event to
POST /pushwith an API key. - Maranduva broadcasts the event to every WebSocket client connected to that channel.
- Clients receive the message in real time, typically in under 10 ms.
Authentication
All HTTP requests must include your API key in the x-api-key header. Contact us to get your API key.
x-api-key: YOUR_API_KEY
Keep your key secret. Never expose your API key in client-side or public code. It is intended for server-side use only.
Publish Event
Creates and publishes an event to the configured broker. All subscribers connected to the specified channel will receive the message in real time.
Request body
Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
| id | UUID | Optional | Unique event identifier. Auto-generated if not provided. |
| channel | string | Required | Channel where the message will be published. Subscribers on this channel receive the event. |
| message | string | Required | Message payload to be sent to all subscribers. |
| app | string | Required | Application identifier. Scopes the channel to your app. |
Example request
curl -X POST 'https://events.maranduva.com/push' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-H 'x-api-key: YOUR_API_KEY' \
-d '{
"channel": "orders",
"message": "order_created",
"app": "my-app"
}'
const response = await fetch('https://events.maranduva.com/push', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': 'YOUR_API_KEY',
},
body: JSON.stringify({
channel: 'orders',
message: 'order_created',
app: 'my-app',
}),
});
const data = await response.json();
console.log(data);
import requests
response = requests.post(
'https://events.maranduva.com/push',
headers={
'content-type': 'application/json',
'x-api-key': 'YOUR_API_KEY',
},
json={
'channel': 'orders',
'message': 'order_created',
'app': 'my-app',
},
)
print(response.json())
<?php
$ch = curl_init('https://events.maranduva.com/push');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: YOUR_API_KEY',
],
CURLOPT_POSTFIELDS => json_encode([
'channel' => 'orders',
'message' => 'order_created',
'app' => 'my-app',
]),
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
body, _ := json.Marshal(map[string]string{
"channel": "orders",
"message": "order_created",
"app": "my-app",
})
req, _ := http.NewRequest("POST", "https://events.maranduva.com/push", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-api-key", "YOUR_API_KEY")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpRequest.BodyPublishers;
var body = """
{"channel":"orders","message":"order_created","app":"my-app"}
""";
var request = HttpRequest.newBuilder()
.uri(URI.create("https://events.maranduva.com/push"))
.header("Content-Type", "application/json")
.header("x-api-key", "YOUR_API_KEY")
.POST(BodyPublishers.ofString(body))
.build();
var response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
require 'net/http'
require 'json'
uri = URI('https://events.maranduva.com/push')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['x-api-key'] = 'YOUR_API_KEY'
request.body = {
channel: 'orders',
message: 'order_created',
app: 'my-app'
}.to_json
response = http.request(request)
puts response.body
using System.Net.Http;
using System.Text;
using System.Text.Json;
var payload = JsonSerializer.Serialize(new {
channel = "orders",
message = "order_created",
app = "my-app"
});
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY");
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://events.maranduva.com/push", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"channel": "orders",
"message": "order_created",
"app": "my-app"
}
If id is omitted, a UUID v4 is automatically generated and returned in the response.
Subscribe via WebSocket
Clients subscribe to one or more channels by opening a WebSocket connection. Any event published to those channels is delivered instantly over the open connection.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| app | string | Required | Your application identifier. |
| channels | string | Required | Comma-separated list of channel IDs to subscribe to. E.g. orders,notifications |
Example connection
wscat -c "wss://channels.maranduva.com/?app=my-app&channels=orders,notifications"
const socket = new WebSocket(
'wss://channels.maranduva.com/?app=my-app&channels=orders,notifications'
)
socket.addEventListener('open', () => {
console.log('Connected to Maranduva')
})
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
console.log('Received:', data)
})
socket.addEventListener('close', () => {
console.log('Disconnected')
})
// npm install ws
import WebSocket from 'ws'
const socket = new WebSocket(
'wss://channels.maranduva.com/?app=my-app&channels=orders,notifications'
)
socket.on('open', () => {
console.log('Connected to Maranduva')
})
socket.on('message', (data) => {
console.log('Received:', JSON.parse(data))
})
socket.on('close', () => {
console.log('Disconnected')
})
# pip install websocket-client
import json
import websocket
def on_open(ws):
print('Connected to Maranduva')
def on_message(ws, message):
data = json.loads(message)
print('Received:', data)
def on_close(ws, code, msg):
print('Disconnected')
ws = websocket.WebSocketApp(
'wss://channels.maranduva.com/?app=my-app&channels=orders,notifications',
on_open=on_open,
on_message=on_message,
on_close=on_close,
)
ws.run_forever()
<?php
// composer require textalk/websocket
use WebSocket\Client;
$client = new Client(
'wss://channels.maranduva.com/?app=my-app&channels=orders,notifications'
);
try {
while (true) {
$message = $client->receive();
$data = json_decode($message, true);
echo 'Received: ' . print_r($data, true) . PHP_EOL;
}
} finally {
$client->close();
}
// go get github.com/gorilla/websocket
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/gorilla/websocket"
)
func main() {
url := "wss://channels.maranduva.com/?app=my-app&channels=orders,notifications"
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Println("Connected to Maranduva")
for {
_, msg, err := conn.ReadMessage()
if err != nil {
log.Println("Disconnected:", err)
return
}
var data map[string]interface{}
json.Unmarshal(msg, &data)
fmt.Println("Received:", data)
}
}
// Java 11+ — jakarta.websocket or tyrus client
import jakarta.websocket.*;
import java.net.URI;
@ClientEndpoint
public class MaranduvaClient {
@OnOpen
public void onOpen(Session session) {
System.out.println("Connected to Maranduva");
}
@OnMessage
public void onMessage(String message) {
System.out.println("Received: " + message);
}
@OnClose
public void onClose(Session session) {
System.out.println("Disconnected");
}
public static void main(String[] args) throws Exception {
var url = "wss://channels.maranduva.com/?app=my-app&channels=orders,notifications";
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
container.connectToServer(MaranduvaClient.class, URI.create(url));
Thread.currentThread().join(); // keep alive
}
}
# gem install faye-websocket
require 'faye/websocket'
require 'eventmachine'
require 'json'
EM.run do
ws = Faye::WebSocket::Client.new(
'wss://channels.maranduva.com/?app=my-app&channels=orders,notifications'
)
ws.on :open do
puts 'Connected to Maranduva'
end
ws.on :message do |event|
data = JSON.parse(event.data)
puts "Received: #{data}"
end
ws.on :close do
puts 'Disconnected'
EM.stop
end
end
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
var uri = new Uri("wss://channels.maranduva.com/?app=my-app&channels=orders,notifications");
var buffer = new byte[4096];
using var ws = new ClientWebSocket();
await ws.ConnectAsync(uri, CancellationToken.None);
Console.WriteLine("Connected to Maranduva");
while (ws.State == WebSocketState.Open)
{
var result = await ws.ReceiveAsync(buffer, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close) break;
var json = Encoding.UTF8.GetString(buffer, 0, result.Count);
var data = JsonSerializer.Deserialize<object>(json);
Console.WriteLine($"Received: {data}");
}
Console.WriteLine("Disconnected");
// build.gradle: implementation("com.squareup.okhttp3:okhttp:4.12.0")
import okhttp3.*
import okio.ByteString
val client = OkHttpClient()
val request = Request.Builder()
.url("wss://channels.maranduva.com/?app=my-app&channels=orders,notifications")
.build()
val listener = object : WebSocketListener() {
override fun onOpen(ws: WebSocket, response: Response) {
println("Connected to Maranduva")
}
override fun onMessage(ws: WebSocket, text: String) {
println("Received: $text")
}
override fun onClosing(ws: WebSocket, code: Int, reason: String) {
ws.close(1000, null)
println("Disconnected")
}
override fun onFailure(ws: WebSocket, t: Throwable, response: Response?) {
t.printStackTrace()
}
}
client.newWebSocket(request, listener)
import Foundation
let url = URL(string: "wss://channels.maranduva.com/?app=my-app&channels=orders,notifications")!
let session = URLSession(configuration: .default)
let task = session.webSocketTask(with: url)
task.resume()
print("Connected to Maranduva")
func receiveMessage() {
task.receive { result in
switch result {
case .success(let message):
switch message {
case .string(let text):
print("Received:", text)
default:
break
}
receiveMessage() // keep listening
case .failure(let error):
print("Disconnected:", error)
}
}
}
receiveMessage()
#import <Foundation/Foundation.h>
NSURL *url = [NSURL URLWithString:
@"wss://channels.maranduva.com/?app=my-app&channels=orders,notifications"];
NSURLSession *session = [NSURLSession sessionWithConfiguration:
[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionWebSocketTask *task =
[session webSocketTaskWithURL:url];
[task resume];
NSLog(@"Connected to Maranduva");
// Recursive receive block
__block void (^receive)(void);
receive = ^{
[task receiveMessageWithCompletionHandler:^(
NSURLSessionWebSocketMessage *msg, NSError *error) {
if (error) {
NSLog(@"Disconnected: %@", error);
return;
}
if (msg.type == NSURLSessionWebSocketMessageTypeString) {
NSLog(@"Received: %@", msg.string);
}
receive();
}];
};
receive();
Need help? Reach out at info@maranduva.com or visit the contact page.