Developers

MunchReach API

Build powerful integrations with the MunchReach API.

Version
2026-08 (public preview)
Base URL
https://munchreach.com
Authentication
Per-connection tokens and HMAC signatures over HTTPS

Overview

The MunchReach API is plain JSON over HTTPS. It is organised around the flows that cross a workspace boundary: bringing leads in, triggering automations, receiving delivery events, and pushing events out to the tools you already use.

Authentication

MunchReach does not issue a single account-wide REST key today. Each entry point carries its own credential, and every credential is scoped to exactly one workspace resource.

Connection tokens (leads)

Connect an integration in Settings → Integrations. MunchReach generates an inbound URL that already contains the token. The token identifies one connection, so a request can only ever write into that workspace. Tokens are at least 16 characters.

Token in the query string
POST https://munchreach.com/api/public/integrations/zapier?token=YOUR_API_TOKEN
Content-Type: application/json

Signed requests (automations & webhooks)

Automation webhooks and the referral billing webhook accept an HMAC-SHA256 hex digest of the raw request body in the x-sendloop-signature header. Sign the exact bytes you transmit — re-serialising JSON changes the digest.

Node.js signature
import { createHmac } from "node:crypto";

const body = JSON.stringify({ email: "ada@example.com" });
const signature = createHmac("sha256", process.env.MUNCHREACH_SIGNING_SECRET)
  .update(body)
  .digest("hex");

await fetch("https://munchreach.com/api/public/automations/YOUR_WEBHOOK_TOKEN", {
  method: "POST",
  headers: { "Content-Type": "application/json", "x-sendloop-signature": signature },
  body,
});

Security recommendations

  • Store tokens and signing secrets server-side; never ship them in browser code.
  • Always call the API over HTTPS.
  • Send an x-idempotency-key so a retry cannot duplicate work.
  • Disable a connection in the dashboard to revoke its token immediately.
  • Use one connection per external system so you can revoke them independently.

Getting started

Send your first lead into a workspace in three steps.

  1. 1Open Settings → Integrations and connect Zapier, Make, Google Sheets or a generic webhook.
  2. 2Copy the inbound URL. It already contains the connection token.
  3. 3POST a JSON body with at least an email field. The response is 200 with { "ok": true }.
First request
curl -X POST "https://munchreach.com/api/public/integrations/zapier?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "x-idempotency-key: YOUR_IDEMPOTENCY_KEY" \
  -d '{"email":"ada@example.com","first_name":"Ada","company":"Example Inc"}'

API reference

Generated from the routes that exist in MunchReach today. Endpoints that the platform calls internally are included so delivery logs are readable, and are labelled as such.

Leads

Push prospects into a workspace from Zapier, Make, Google Sheets, a form or any HTTP client. This is the only endpoint that creates leads over HTTP.

Create or update a lead

POST/api/public/integrations/{slug}

Upserts a lead into the workspace that owns the integration connection. Matching is on workspace + email, so sending the same address twice updates the existing lead instead of duplicating it. The connection slug is recorded as the lead source.

Authentication

Connection token in the `token` query parameter (minimum 16 characters). The token identifies exactly one connection and therefore exactly one workspace.

path parameters

NameTypeRequiredDescription
slugstringRequiredIntegration slug of the connection, for example `zapier`, `make` or `google-sheets`.

query parameters

NameTypeRequiredDescription
tokenstringRequiredInbound token generated when the integration was connected.

header parameters

NameTypeRequiredDescription
x-idempotency-keystringOptionalOptional de-duplication key. Repeat deliveries with the same key are acknowledged without being processed twice.

Request body fields

NameTypeRequiredDescription
emailstring (email, max 320)RequiredProspect email address. Stored lowercased.
first_namestring (max 120)OptionalGiven name.
last_namestring (max 120)OptionalFamily name.
companystring (max 200)OptionalCompany name.
job_titlestring (max 200)OptionalJob title.
phonestring (max 60)OptionalPhone number.
websitestring (max 300)OptionalCompany website.

Request body

JSON
{
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Lovelace",
  "company": "Example Inc",
  "job_title": "Head of Growth",
  "phone": "+1 555 0100",
  "website": "https://example.com"
}
Example request · cURL
curl -X POST "https://munchreach.com/api/public/integrations/zapier?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "x-idempotency-key: YOUR_IDEMPOTENCY_KEY" \
  -d '{
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Lovelace",
  "company": "Example Inc",
  "job_title": "Head of Growth",
  "phone": "+1 555 0100",
  "website": "https://example.com"
}'
Example response
{
  "ok": true
}

Error responses

401{ "error": "Missing token" }

No token, or shorter than 16 characters.

Send the full token from Settings → Integrations.

401{ "error": "Invalid token" }

No connection matches this slug and token.

Reconnect the integration and copy the new URL.

403{ "error": "Connection disabled" }

The connection exists but is disabled.

Re-enable it in the dashboard.

400{ "error": "Invalid JSON body" }

The body could not be parsed as JSON.

Send `Content-Type: application/json` with valid JSON.

400{ "error": "Invalid payload", "details": [ … ] }

A field failed validation. Up to five issues are returned.

Fix the reported fields; `email` must be a valid address.

500{ "error": "Could not store the lead" }

The lead could not be written.

Retry with the same `x-idempotency-key`.

  • A duplicate delivery returns `200 { "ok": true, "deduplicated": true }` instead of writing again.
  • When no `x-idempotency-key` header is sent, the key defaults to the connection id plus the lowercased email.
  • Every attempt is written to the integration event log and is visible in Settings → Integrations.

Implementation: src/routes/api/public/integrations/$slug.ts

CORS preflight

OPTIONS/api/public/integrations/{slug}

Returns the CORS headers for browser-based callers. Allows any origin, the POST and OPTIONS methods, and the `content-type` and `x-idempotency-key` headers.

Authentication

None.

Example request · cURL
curl -X OPTIONS "https://munchreach.com/api/public/integrations/zapier"
Example response
204 No Content
access-control-allow-origin: *
access-control-allow-methods: POST, OPTIONS
access-control-allow-headers: content-type, x-idempotency-key

Implementation: src/routes/api/public/integrations/$slug.ts

Automations

Trigger an automation workflow from an external system. Each active automation with a webhook trigger has its own URL token.

Trigger an automation

POST/api/public/automations/{token}

Emits a `webhook.received` event for the automation that owns the token. When the JSON body contains an `email` field that matches a lead in the workspace, the run is linked to that lead.

Authentication

The URL token identifies the automation. When the automation has a signing secret, an HMAC-SHA256 hex digest of the raw body must also be sent in the `x-sendloop-signature` header.

path parameters

NameTypeRequiredDescription
tokenstring (16–64 hex chars)RequiredWebhook token shown on the automation page.

header parameters

NameTypeRequiredDescription
x-sendloop-signaturestring (hex)OptionalRequired only when a signing secret is configured: `hmac_sha256(secret, raw_body)`.
x-idempotency-keystringOptionalOptional de-duplication key. Repeat deliveries with the same key are acknowledged without being processed twice.

Request body

JSON
{
  "email": "ada@example.com",
  "source": "your-system",
  "any_other_field": "is passed through to the automation"
}
Example request · cURL
curl -X POST "https://munchreach.com/api/public/automations/YOUR_WEBHOOK_TOKEN" \
  -H "Content-Type: application/json" \
  -H "x-sendloop-signature: YOUR_SIGNATURE" \
  -H "x-idempotency-key: YOUR_IDEMPOTENCY_KEY" \
  -d '{
  "email": "ada@example.com",
  "source": "your-system",
  "any_other_field": "is passed through to the automation"
}'
Example response
{
  "ok": true,
  "enqueued": 1
}

Error responses

404{ "error": "Not found" }

Token is malformed or does not match an automation. Unknown and inactive tokens look identical on purpose.

Copy the URL again from the automation page.

401{ "error": "Invalid signature" }

The signature header did not match the HMAC of the raw body.

Sign the exact bytes you send, before any re-serialisation.

400{ "error": "Body must be JSON" }

A non-empty body was not valid JSON.

Send valid JSON, or send an empty body.

413{ "error": "Payload too large" }

The body exceeded 100,000 characters.

Send a smaller payload.

202{ "ok": true, "skipped": "Automation is not active" }

The automation exists but is paused or draft.

Activate the automation to process events.

  • An empty body is accepted; the automation then runs with no payload fields.
  • Only JSON objects are read — arrays and scalars are ignored and treated as an empty payload.

Implementation: src/routes/api/public/automations/$token.ts

Email delivery & replies

The endpoint MunchReach exposes to its sending infrastructure for delivery events and inbound replies. Documented because it appears in delivery logs; it is not intended for customer traffic.

Inbound endpoint health

GET/api/public/inbound/email

Health probe. Confirms the inbound receiver is reachable.

Authentication

None.

Example request · cURL
curl -X GET "https://munchreach.com/api/public/inbound/email"
Example response
{
  "ok": true,
  "endpoint": "inbound-email-webhook",
  "method": "POST"
}

Implementation: src/routes/api/public/inbound/email.ts

Deliver an inbound email or delivery event

POST/api/public/inbound/email

Accepts inbound mail and delivery events (sent, delivered, opened, clicked, bounced, complained, replied). Every delivery is signature-verified and logged before processing, and processing is idempotent, so retries never duplicate a message or a metric.

Authentication

Signature required: a provider Svix signature, an HMAC of the raw body, or the MunchReach shared secret sent as `x-munchreach-signature`, `x-sendloop-signature` or a bearer token.

header parameters

NameTypeRequiredDescription
content-typestringOptional`application/json` is parsed as JSON; anything else is parsed as form-encoded.
Example request · cURL
curl -X POST "https://munchreach.com/api/public/inbound/email"
Example response
{
  "ok": true
}

Error responses

401{ "error": "…", "signature": "invalid" }

Signature verification failed.

Check the signing secret configured for the sender.

503{ "error": "…", "signature": "failed" }

Verification could not be completed.

Retry; the sender should back off and retry.

400{ "error": "Invalid body" }

The body was neither JSON nor form-encoded.

Fix the payload encoding.

500{ "error": "Processing failed" }

An infrastructure error occurred after verification.

Retry — processing is idempotent.

  • Unmatched mail is acknowledged with 200 so the sender does not retry indefinitely.
  • Failed signature checks raise an internal alert and are visible to platform admins.

Not fully documented: The event payload shape is defined by the sending provider rather than by MunchReach, so no fixed request schema is published here.

Implementation: src/routes/api/public/inbound/email.ts

Billing webhooks

Receivers for the payment providers MunchReach supports. Configure these URLs in the provider dashboard; they are not called by customers.

Stripe webhook

POST/api/public/billing/stripe

Processes Stripe subscription, invoice, payment and refund events. Each event id is stored once, so replays cannot double-apply a change.

Authentication

Stripe signature verification against the configured webhook signing secret.

Example request · cURL
curl -X POST "https://munchreach.com/api/public/billing/stripe"
Example response
{
  "received": true
}

Error responses

400{ "error": "…" }

The payload could not be verified or parsed.

Check the signing secret in the provider settings.

  • `GET` on the same path returns a health response.

Implementation: src/routes/api/public/billing/stripe.ts

Paddle webhook

POST/api/public/billing/paddle

Processes Paddle subscription and transaction events with signature verification and idempotent storage.

Authentication

Paddle signature verification against the configured webhook secret.

Example request · cURL
curl -X POST "https://munchreach.com/api/public/billing/paddle"
Example response
{
  "received": true
}

Error responses

400{ "error": "…" }

Verification or parsing failed.

Check the configured webhook secret.

  • `GET` on the same path returns a health response.

Implementation: src/routes/api/public/billing/paddle.ts

Lemon Squeezy webhook

POST/api/public/billing/lemonsqueezy

Processes Lemon Squeezy subscription and order events with signature verification and idempotent storage.

Authentication

Lemon Squeezy signature verification against the configured webhook secret.

Example request · cURL
curl -X POST "https://munchreach.com/api/public/billing/lemonsqueezy"
Example response
{
  "received": true
}

Error responses

400{ "error": "…" }

Verification or parsing failed.

Check the configured webhook secret.

  • `GET` on the same path returns a health response.

Implementation: src/routes/api/public/billing/lemonsqueezy.ts

Referral commission webhook

POST/api/public/referral/billing

Credits referral commissions from a billing event. The body is HMAC-verified before anything is written and every event id is stored once, so a replayed event cannot create a second commission.

Authentication

HMAC-SHA256 of the raw body in the `x-sendloop-signature` header, using the referral webhook secret.

header parameters

NameTypeRequiredDescription
x-sendloop-signaturestring (hex)Required`hmac_sha256(secret, raw_body)`.
Example request · cURL
curl -X POST "https://munchreach.com/api/public/referral/billing" \
  -H "x-sendloop-signature: YOUR_SIGNATURE"
Example response
{
  "ok": true
}

Error responses

401{ "error": "Invalid signature" }

Signature mismatch.

Sign the raw request body with the referral secret.

  • Every delivery attempt is logged for the admin health panel.

Implementation: src/routes/api/public/referral/billing.tsx

Media

Stable, cacheable URLs for blog media. The underlying storage bucket stays private.

Fetch a media file

GET/api/public/media/{path}

Signs a short-lived storage URL server-side and streams the bytes back. Responses are cacheable for one hour in the browser and one day at the edge.

Authentication

None — only files inside the blog media bucket are reachable.

path parameters

NameTypeRequiredDescription
pathstringRequiredObject path inside the blog media bucket. Paths containing `..` are rejected.
Example request · cURL
curl -X GET "https://munchreach.com/api/public/media/posts/cover.png"
Example response
200 OK
Content-Type: image/png
Cache-Control: public, max-age=3600, s-maxage=86400

<binary image data>

Error responses

404Not found

Missing, unreadable or traversal path.

Check the object path.

Implementation: src/routes/api/public/media.$.ts

Scheduler (campaigns, warmup & jobs)

Background runners that keep campaigns, warmup and maintenance jobs moving. They are called by the platform scheduler with a shared secret and are listed here for completeness — customer integrations do not call them.

Run a scheduled job

POST/api/public/cron/{job}

Runs one background job. Available jobs: `run-campaigns`, `run-warmup`, `run-automations`, `run-ai-personalization`, `run-transactional-email`, `reconcile-delivery`, `retry-inbound`, `recheck-domains`, `provider-health`. Both GET and POST are accepted.

Authentication

Shared scheduler secret, sent as `x-sendloop-signature`, `Authorization: Bearer …` or `?token=`.

path parameters

NameTypeRequiredDescription
jobstringRequiredOne of the job names listed above.

query parameters

NameTypeRequiredDescription
tokenstringOptionalScheduler secret, if not sent as a header.
Example request · cURL
curl -X POST "https://munchreach.com/api/public/cron/run-campaigns?token=YOUR_API_TOKEN"
Example response
{
  "ok": true
}

Error responses

401{ "error": "Unauthorized" }

The scheduler secret did not match.

Use the configured scheduler token.

503{ "error": "Runner is not configured" }

No scheduler secret is configured.

Configure the runner token in platform settings.

  • Each job returns a per-job summary object alongside `ok`.

Implementation: src/routes/api/public/cron/*.ts

Webhooks

MunchReach delivers events to the webhook URL or CRM stored on a connection. Payloads are POSTed as JSON; Slack connections receive a formatted message instead.

reply.receivedEmitted today

A prospect replies to a campaign email and the reply lands in the unified inbox.

Example payload
{
  "event": "reply.received",
  "lead": {
    "email": "ada@example.com",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "company": "Example Inc"
  }
}
reply.positiveEmitted today

A reply is classified as positive. Fires in addition to reply.received.

Example payload
{
  "event": "reply.positive",
  "lead": {
    "email": "ada@example.com",
    "first_name": "Ada",
    "company": "Example Inc"
  }
}
lead.createdDeclared, not emitted yet

Declared by CRM and webhook integrations in the integration catalogue.

Example payload
{
  "event": "lead.created",
  "lead": { "email": "ada@example.com" }
}
campaign.completedDeclared, not emitted yet

Declared by webhook and CRM integrations in the integration catalogue.

Example payload
{
  "event": "campaign.completed",
  "lead": { "email": "ada@example.com" }
}
meeting.bookedDeclared, not emitted yet

Declared by calendar, CRM and webhook integrations in the integration catalogue.

Example payload
{
  "event": "meeting.booked",
  "lead": { "email": "ada@example.com" }
}
email.bouncedDeclared, not emitted yet

Declared by webhook and CRM integrations in the integration catalogue.

Example payload
{
  "event": "email.bounced",
  "lead": { "email": "ada@example.com" }
}
integration.connectedEmitted today

Sent once as a test payload when a webhook integration is connected or tested.

Example payload
{
  "event": "integration.connected",
  "source": "sendloop",
  "message": "MunchReach connection test — you can ignore this message.",
  "timestamp": "2026-08-22T21:14:05.000Z"
}

Delivery, retries and idempotency

  • Deliveries are POSTed with a 10 second timeout. Any 2xx counts as accepted.
  • Each attempt is written to the integration event log with its status and message, and the connection is marked error when a delivery fails.
  • Events carry an idempotency key where one is available (for example reply:<conversation>:<timestamp>), so store it and ignore repeats.
  • Inbound webhooks you send to MunchReach are verified before processing and are safe to retry.

API errors

Errors are returned as JSON with an error field. These are the statuses the public endpoints actually return.

StatusExample bodyMeaningWhat to do
401
POST
{ "error": "Missing token" }No token, or shorter than 16 characters.Send the full token from Settings → Integrations.
401
POST
{ "error": "Invalid token" }No connection matches this slug and token.Reconnect the integration and copy the new URL.
403
POST
{ "error": "Connection disabled" }The connection exists but is disabled.Re-enable it in the dashboard.
400
POST
{ "error": "Invalid JSON body" }The body could not be parsed as JSON.Send `Content-Type: application/json` with valid JSON.
400
POST
{ "error": "Invalid payload", "details": [ … ] }A field failed validation. Up to five issues are returned.Fix the reported fields; `email` must be a valid address.
500
POST
{ "error": "Could not store the lead" }The lead could not be written.Retry with the same `x-idempotency-key`.
404
POST
{ "error": "Not found" }Token is malformed or does not match an automation. Unknown and inactive tokens look identical on purpose.Copy the URL again from the automation page.
401
POST
{ "error": "Invalid signature" }The signature header did not match the HMAC of the raw body.Sign the exact bytes you send, before any re-serialisation.
400
POST
{ "error": "Body must be JSON" }A non-empty body was not valid JSON.Send valid JSON, or send an empty body.
413
POST
{ "error": "Payload too large" }The body exceeded 100,000 characters.Send a smaller payload.
202
POST
{ "ok": true, "skipped": "Automation is not active" }The automation exists but is paused or draft.Activate the automation to process events.
401
POST
{ "error": "…", "signature": "invalid" }Signature verification failed.Check the signing secret configured for the sender.
503
POST
{ "error": "…", "signature": "failed" }Verification could not be completed.Retry; the sender should back off and retry.
400
POST
{ "error": "Invalid body" }The body was neither JSON nor form-encoded.Fix the payload encoding.
500
POST
{ "error": "Processing failed" }An infrastructure error occurred after verification.Retry — processing is idempotent.
400
POST
{ "error": "…" }The payload could not be verified or parsed.Check the signing secret in the provider settings.
400
POST
{ "error": "…" }Verification or parsing failed.Check the configured webhook secret.
400
POST
{ "error": "…" }Verification or parsing failed.Check the configured webhook secret.
401
POST
{ "error": "Invalid signature" }Signature mismatch.Sign the raw request body with the referral secret.
404
GET
Not foundMissing, unreadable or traversal path.Check the object path.
401
POST
{ "error": "Unauthorized" }The scheduler secret did not match.Use the configured scheduler token.
503
POST
{ "error": "Runner is not configured" }No scheduler secret is configured.Configure the runner token in platform settings.

Rate limits & usage

There is no per-key HTTP rate limit implemented on the public endpoints today, so no numeric limit is published here.

  • Automation webhook bodies are capped at 100,000 characters; larger payloads return 413.
  • Outbound webhook deliveries time out after 10 seconds.
  • Sending volume is governed by your plan entitlements (emails and leads), not by the API. Current usage is shown on the billing page in your dashboard.
  • Treat any 5xx as retryable and use exponential backoff with a stable idempotency key.

Coverage & changelog

What the public API does not cover yet, so you can plan around it.

Dashboard-only today

  • Campaign creation, start and pause
  • Sending accounts and domains
  • Conversations and the unified inbox
  • Contacts, lists and segments
  • Warmup configuration
  • Usage and plan entitlements

These run through the authenticated application rather than a public HTTP endpoint, so no REST documentation is published for them yet.

Changelog

2026-08 (public preview) — First published reference: leads, automations, email delivery, billing webhooks, media and scheduler endpoints, plus the outbound integration events.