API reference

The MessageBlue iMessage Gateway REST API
View as Markdown

The iMessage Gateway API routes send jobs to registered Mac agents. Every page in this reference is generated from the gateway’s OpenAPI specification, so it always matches what the service actually accepts.

Base URL: https://api.messageblue.ai

Authentication

Core endpoints use HMAC request signing. You send your App ID in the clear and prove possession of your App Secret by signing each request — the App Secret is never transmitted.

Every signed request carries four headers:

HeaderValue
X-App-IdYour app id
X-TimestampUnix time in milliseconds at signing
X-NonceA UUID, unique per request (replays are rejected)
AuthorizationHMAC <lowercase-hex-signature>

The signature is computed over the request method, path, body hash, timestamp and nonce:

bodyHash = sha256hex( canonicalJson(body) || "" )
toSign = [ METHOD, path, bodyHash, X-Timestamp, X-Nonce ].join("\n")
signature = HMAC_SHA256( appSecret, toSign )

The timestamp must be within a five-minute window of the gateway’s clock.

The official TypeScript and Python SDKs sign every request for you. Reach for the manual scheme above only if you’re calling the API from another language.

What you can do

Messages

Send texts, attachments and voice notes to individuals or groups, react with tapbacks, send typing indicators and read receipts, and check whether a number is iMessage-capable.

Agents

Register, assign, reassign, block and inspect the Mac agents that send on your behalf.

Mappings

Inspect and manage the user⇄agent number mappings that decide which agent handles a given contact.

Webhooks

Configure where inbound messages, tapbacks and delivery-status updates are delivered, and verify their signatures.

Analytics (conversation history, contacts, per-number stats), queue status and health endpoints round out the surface.

Webhooks

MessageBlue delivers three events — inbound_message, inbound_reaction and message status updates.

Where they go

Register your destinations with a single call:

$POST /settings/webhooks
${
> "inboundWebhookUrl": "https://example.com/webhooks/inbound",
> "statusWebhookUrl": "https://example.com/webhooks/status"
>}

inboundWebhookUrl receives both inbound events; statusWebhookUrl receives delivery-status updates. Send only the field you want to change — an empty value unsets that webhook. A per-message status_callback set at send time takes precedence over statusWebhookUrl.

Verifying them

Every webhook is signed. Each request carries:

  • X-MB-Event-Id — a unique evt_... id for the event; use it to deduplicate
  • X-MB-Timestamp — unix seconds when the request was signed
  • X-MB-Signaturev1=<hex>, where <hex> is HMAC-SHA256(secret, "{timestamp}.{rawBody}")

Verify the signature before trusting a payload, and sign over the raw request bytes — re-serializing the JSON first produces a different hash and the check will fail. Generate your signing secret in your MessageBlue dashboard.

Sample payloads

1{
2 "type": "inbound_message",
3 "messageId": "9c8f1e42-5b7a-4d31-8f60-2ab4c9d17e83",
4 "text": "Hey — is my order still arriving today?",
5 "from": "+14155550123",
6 "to": "+16475137145",
7 "outbound": false,
8 "iMessageNumber": "+16475137145",
9 "participants": ["+14155550123", "+16475137145"],
10 "chatGuid": "iMessage;-;+14155550123",
11 "groupId": null,
12 "messageGuid": "6F1C2A54-6C1E-4E2B-9E0A-2B7C1D3E4F50",
13 "attachments": [],
14 "timestamp_ms": 1769472000000,
15 "accountId": "acct_4f9c2b7e1d8a"
16}

The status payload has no type field — identify it by status, which progresses through values like sent, delivered, read, failed and SMS_FALLBACK.

SDKs

$npm install messageblue
1import { createMessageBlueClient } from "messageblue";
2
3const client = createMessageBlueClient({
4 appId: process.env.MB_APP_ID!,
5 appSecret: process.env.MB_APP_SECRET!, // never sent — signs requests
6 environment: "https://api.messageblue.ai",
7});
8
9await client.messages.sendIndividual({ to: "+16475137145", text: "Hello!" });

Trying requests

Signed endpoints have no in-page “Try it” button. Request signing has to happen after you fill in a request body — the signature covers it — and this reference can’t do that computation, so a button here would only ever return 401.

Use the API Console instead. It’s served by the gateway, signs every request with your App Secret, and covers the same endpoints:

Your App Secret stays in your browser — the console uses it to compute a signature locally and never transmits it.

GET /health is the one endpoint that needs no signature, so it keeps a working Try-it button here.