Home Documentation

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:

  1. Your server (or any HTTP client) publishes an event to POST /push with an API key.
  2. Maranduva broadcasts the event to every WebSocket client connected to that channel.
  3. 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.

HTTP Header
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.

POST /push

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());
JSON — with custom ID
{
  "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.

WS wss://channels.maranduva.com/

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.