Developer platform

marv.Inbox Developer API

Connect any CRM, point of sale, ERP, or in-house app to your team's WhatsApp and Messenger inbox. A REST API over OAuth2 for reading and writing data, plus signed webhooks that push events to you in real time. Every workspace can create its own credentials in Settings, Developer. No partner program and no sales call required.

Base URL: https://api.marvinbox.com

What you can build

Sync customers and conversations

Upsert contacts from your CRM, look them up by phone or your own id, and read conversations and message history.

Send messages and campaigns

Send a reply or an approved WhatsApp template into a conversation, or launch an outbound campaign to a contact list.

Create tasks and log events

Open tasks for your team and write CRM timeline events, such as an order placed or an invoice overdue, onto a contact.

Receive real-time webhooks

Subscribe to inbound messages, conversation changes, task updates, and campaign events. Every delivery is HMAC signed.

Base URL and access

The API is live in production today. You do not need an upgrade or a separate developer account.

Base URL
https://api.marvinbox.com
Getting credentials
Sign in to app.marvinbox.com and open Settings, Developer. Create an integration client to get a client id and client secret for the API, and register a webhook endpoint to receive events. A workspace admin role is required to manage both.

Quickstart

Three steps from zero to your first authenticated call.

  1. 1

    Create an integration client

    In Settings, Developer, Integration clients, create a client and pick the scopes it needs. Copy the client id and client secret. The secret is shown only once.

  2. 2

    Exchange credentials for a token

    Call the token endpoint with your client id and secret. You get back a short lived bearer token, valid for one hour.

  3. 3

    Call the API

    Send the token as an Authorization header on every request. The token already identifies your workspace, so there is no tenant header to set.

curl -X POST https://api.marvinbox.com/external/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "marv_ic_live_...",
    "client_secret": "marv_cs_..."
  }'

Response

{
  "access_token": "<jwt>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "customers:write messages:write"
}
curl -X POST https://api.marvinbox.com/external/v1/customers/upsert \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: <unique-key>" \
  -d '{
    "phone": "+972501234567",
    "name": "Jane Doe",
    "externalCustomerId": "crm-001"
  }'

Authentication

Authentication uses the OAuth2 client credentials grant. Post your client id and secret to the token endpoint and receive a bearer token, a signed JWT, valid for 3600 seconds.

Send that token as Authorization: Bearer <token> on every external/v1 request. The token is bound to the workspace that owns the client, so a token for one workspace can never read another, and you never send a tenant slug.

Scopes are checked live against the client on every call, so narrowing a client scope or rotating its secret takes effect immediately.

Scopes

Grant a client only the scopes it needs. Each endpoint below lists its required scope.

ScopeGrants
customers:readLook up contacts
customers:writeCreate and update contacts
conversations:readList and read conversations
conversations:writeReserved for future use
messages:readRead message history
messages:writeSend messages and templates
events:writeIngest CRM timeline events
tasks:readList and read tasks
tasks:writeCreate and update tasks
campaigns:writeCreate outbound campaigns

REST endpoints

All endpoints are under the base URL above. Writes accept an Idempotency-Key header.

MethodEndpointScope
POST/external/oauth/token

Exchange client credentials for an access token

none
POST/external/v1/customers/upsert

Create or update a contact by phone or external id

customers:write
GET/external/v1/customers/lookup

Find a contact by phone or external id

customers:read
GET/external/v1/contacts

List contacts (org, chat-window flag, last message), by last activity

customers:read
POST/external/v1/contacts/messages

Read a contact’s messages grouped per phone

messages:read
GET/external/v1/templates

List approved WhatsApp templates (with template id)

messages:read
POST/external/v1/messages/send

Send by phone: a text reply or an approved template

messages:write
POST/external/v1/events

Log a CRM timeline event (alias /interactions)

events:write
POST/external/v1/outbound/campaigns

Create an outbound campaign

campaigns:write
POST/external/v1/tasks

Create a task

tasks:write
GET/external/v1/tasks

List tasks

tasks:read
PATCH/external/v1/tasks/:id

Update a task

tasks:write

Idempotency

Send an Idempotency-Key header on any write. If you retry with the same key, marv.Inbox returns the stored result of the first call instead of performing the action twice. Use a fresh, unique key per logical operation.

Rate limits and IP allowlist

Each client is limited to 120 requests per minute by default. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining, and a 429 is returned when the limit is exceeded. You can also restrict a client to a list of allowed IP ranges (CIDR) when you create it.

Webhooks

Webhooks push events to your server the moment they happen, so you do not have to poll.

Register an endpoint

In Settings, Developer, Webhooks, add your receiver URL and choose the events you want. Copy the signing secret shown on creation. You can register up to ten endpoints, enable or disable each one, and review recent delivery attempts in the same screen.

How deliveries arrive

Each event is a POST with a JSON body and these headers:

POST /your-receiver HTTP/1.1
Content-Type: application/json
X-Marv-Event: message.created
X-Marv-Delivery: 7c9e6679-7425-40de-944b-e07fc1f90ae7
X-Marv-Timestamp: 2026-06-15T10:23:45.123Z
X-Marv-Signature: v1=3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d...

A 2xx response marks the delivery successful. marv.Inbox waits up to 10 seconds for your response and retries once after 5 seconds on failure. Receiver URLs must be public; private and loopback addresses are rejected.

Event types

Subscribe to any combination of these events.

EventSent when
message.createdA message was sent or received
conversation.updatedConversation metadata changed
conversation.assignedConversation assignment changed
conversation.closedA conversation was closed
conversation.reopenedA closed conversation was reopened

Verifying a delivery

Every delivery is signed so you can trust it came from marv.Inbox. The signature is computed over the raw request body:

v1=HMAC-SHA256(secret, "{X-Marv-Timestamp}.{X-Marv-Delivery}.{rawBody}")

Recompute it against the exact bytes you received, before JSON parsing, reject anything where the timestamp is more than five minutes old, dedupe by X-Marv-Delivery, and compare in constant time.

import { createHmac, timingSafeEqual } from 'crypto';

// marv.Inbox signs every delivery:
//   v1=HMAC-SHA256(secret, `${timestamp}.${deliveryId}.${rawBody}`)
// Verify against the EXACT raw request body, before JSON parsing.
export function verifyMarvWebhook(headers, rawBody, secret) {
  const signature  = headers['x-marv-signature'];  // "v1=<hex>"
  const timestamp  = headers['x-marv-timestamp'];   // ISO 8601
  const deliveryId = headers['x-marv-delivery'];    // UUID, dedupe on this

  // Reject anything older than 5 minutes (replay protection).
  if (Math.abs(Date.now() - Date.parse(timestamp)) > 300_000) return false;

  const expected = 'v1=' + createHmac('sha256', secret)
    .update(`${timestamp}.${deliveryId}.${rawBody}`)
    .digest('hex');

  const a = Buffer.from(signature ?? '', 'utf8');
  const b = Buffer.from(expected, 'utf8');
  return a.length === b.length && timingSafeEqual(a, b);
}

Errors

Errors use standard HTTP status codes with a JSON body describing the problem.

CodeMeaning
400Invalid request body or a missing required field
401Missing, invalid, or expired access token
403Token lacks the required scope, or the caller IP is not allowlisted
404Unknown workspace or resource
409Conflict, for example a colliding externalCustomerId
429Rate limit exceeded (see X-RateLimit-Remaining)

Frequently asked questions

Do I need a partner program or a sales call to use the API?+

No. Any workspace can create integration clients and webhook endpoints in Settings, Developer. There is no application process and the API is not gated to a higher plan.

How do I connect a CRM or help desk to marv.Inbox?+

Upsert your contacts with customers/upsert, log activity with events, open follow ups with tasks, and send replies or approved templates with messages/send. To receive inbound messages and updates in your own system, register a webhook endpoint and subscribe to the events you care about.

How do I verify that a webhook really came from marv.Inbox?+

Recompute the HMAC-SHA256 signature over "{timestamp}.{deliveryId}.{rawBody}" with your endpoint secret and compare it to the X-Marv-Signature header in constant time. Reject deliveries whose X-Marv-Timestamp is more than five minutes old and dedupe on X-Marv-Delivery.

What is the difference between an integration client and a webhook endpoint?+

An integration client is the credential your code uses to call the API: outbound, from you to marv.Inbox. A webhook endpoint is a URL marv.Inbox calls when something happens: inbound, from marv.Inbox to you. Most integrations use both.

How do I rotate or revoke access?+

Rotate an integration client secret or revoke the client entirely in Settings, Developer. Rotation invalidates the old secret immediately. For webhooks, disable or delete the endpoint to stop deliveries.

Does sending messages respect the WhatsApp 24 hour window?+

Yes. A free form WhatsApp message is only allowed within 24 hours of the customer last message. Outside that window, send an approved template instead. The API enforces this for you.

Start building

Create your first integration client and webhook endpoint in the app, or tell us what you are connecting and we will help you scope it.