DocsOpen Moon AI
Browse the docs

Routine webhooks

Start a routine from Zapier, Make, n8n or your own code with a signed request: address, headers, HMAC signature, examples, responses and errors.

On this page

A webhook starts a routine when another service (Zapier, Make, n8n, a script, your own server) sends a signed request to its address. The request’s JSON body is passed to the routine along with its prompt, as data. The request gets an answer right away; the run happens afterward, and its result lands in the routine’s conversation.

Create a triggerLink to the Create a trigger section

  1. Open the routine in “Routines”, then the “Webhooks” tab.
  2. Click “Create a webhook trigger”. The “Name” is optional: it helps you tell your triggers apart, for example “Zapier new leads”.
  3. Copy the “Webhook URL” and the “Signing key”.
  4. Click “I saved the key”.

“Show a call example” displays a ready-to-copy command with your address and key.

Each routine can have up to 5 triggers. The “Webhooks” tab shows how many calls each trigger accepts per minute and per day: the daily number depends on the workspace plan (see the plans).

Endpoint referenceLink to the Endpoint reference section

POST /api/v1/routines/hooks/{id}Link to the POST /api/v1/routines/hooks/{id} section

Starts a run of the routine linked to the trigger.

ItemValue
Full addresshttps://chat.realmoon.ai/api/v1/routines/hooks/<id>, given in the “Webhooks” tab. It contains no secret.
AuthenticationThe request’s HMAC signature. No session token or API key is used.
ScopeA single routine: the trigger’s. The routine’s owner must be an active member of its workspace, and the workspace plan must include routines.
BodyJSON, 64 KB at most. An empty body counts as {}.

ParametersLink to the Parameters section

NameLocationTypeRequiredDescription
idPathStringYesThe trigger’s public ID, 16 to 64 characters (letters, digits, _ and -).
Content-TypeHeaderStringYesapplication/json, or an application/…+json type.
X-Moon-TimestampHeaderStringYesThe time of the call in Unix seconds, digits only. It must be within 5 minutes of the server time, either way.
X-Moon-SignatureHeaderStringYesv1= followed by the signature in lowercase hexadecimal (64 characters). Several signatures can be separated by commas.
BodyBodyJSON objectNoThe event data, passed to the routine.

ResponseLink to the Response section

An accepted request returns 202 before the routine runs:

202 Accepted
{"run_id": "4b0c1f0e-2a7d-4c41-9e3b-6f1d2c8a5b90", "status": "queued"}
FieldTypeDescription
run_idStringThe run ID, visible in the routine’s “History” tab.
statusStringAlways queued: the run is waiting its turn.

The run appears in the routine’s “History” tab with the “Webhook” trigger. Keep the sending machine’s clock accurate.

The signatureLink to the The signature section

The signature is an HMAC-SHA256:

  • the key is the signing key, exactly as shown (it starts with mwk_);
  • the message is the timestamp, a period, then the raw body, exactly the bytes sent;
  • the result is written in lowercase hexadecimal, 64 characters.
Signed message
<X-Moon-Timestamp>.<raw body>

To check your computation, use these sample values:

ItemValue
Keymwk_exempleDeCle
X-Moon-Timestamp1762300800
Body{"event":"new_lead","id":"evt_123"}
Expected signature0d61b9fd29504eb3bd85a2e00361373c8f786822ddb73162bb825ca45abfcb67

The X-Moon-Signature header can hold up to 5 signatures separated by commas (v1=…,v1=…): the request is accepted if one of them matches. A signature without the v1= prefix is accepted too.

ExamplesLink to the Examples section

Replace the address and the key with your trigger’s.

cURL
KEY='mwk_your_key'
URL='https://chat.realmoon.ai/api/v1/routines/hooks/your_id'
BODY='{"event":"new_lead","id":"evt_123"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$KEY" -hex | sed 's/^.* //')
curl -sS -X POST "$URL" \
  -H 'Content-Type: application/json' \
  -H "X-Moon-Timestamp: $TS" \
  -H "X-Moon-Signature: v1=$SIG" \
  --data-binary "$BODY"
Python
import hashlib, hmac, json, time, requests

key = "mwk_your_key"
url = "https://chat.realmoon.ai/api/v1/routines/hooks/your_id"
body = json.dumps({"event": "new_lead", "id": "evt_123"}).encode()
ts = str(int(time.time()))
sig = hmac.new(key.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()

r = requests.post(url, data=body, headers={
    "Content-Type": "application/json",
    "X-Moon-Timestamp": ts,
    "X-Moon-Signature": f"v1={sig}",
})
print(r.status_code, r.json())
JavaScript
import { createHmac } from 'node:crypto';

const key = 'mwk_your_key';
const url = 'https://chat.realmoon.ai/api/v1/routines/hooks/your_id';
const body = JSON.stringify({ event: 'new_lead', id: 'evt_123' });
const ts = Math.floor(Date.now() / 1000).toString();
const sig = createHmac('sha256', key).update(`${ts}.${body}`).digest('hex');

const res = await fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Moon-Timestamp': ts,
    'X-Moon-Signature': `v1=${sig}`
  },
  body
});
console.log(res.status, await res.json());

In Python, pass the already encoded body in data= (not json=), so the signed bytes are what gets sent. The JavaScript example runs on Node.js 18 or later.

In Zapier, Make or n8n, use a code step or an HTTP request step that computes the signature on the exact body, then sends it with both headers.

ErrorsLink to the Errors section

An error body has the form {"detail": …}. The detail is either a text or a {"code": …, "message": …} object. The texts are returned in French; their meaning is given in parentheses.

StatusCode or textCause
400stale_timestampX-Moon-Timestamp is more than 5 minutes off the server time.
400invalid_jsonThe body is not valid JSON.
403feature_scheduled_routinesThe workspace plan does not include routines.
404Introuvable. (“Not found.”)Unknown address, trigger disabled or deleted, signature missing or wrong, timestamp missing or malformed, routine paused or deleted, or an owner who can no longer run it.
409replayedThis request was already received (same signature).
413Corps de requête trop volumineux (64 Ko au plus). (“Request body too large, 64 KB at most.”)The body is larger than 64 KB.
415Le corps doit être du JSON (application/json). (“The body must be JSON.”)Content-Type is not a JSON type.
429rate_limited, Limite de ce déclencheur atteinte. (“This trigger’s limit is reached.”)The trigger received too many calls within the minute or the day.
429rate_limited, Trop de requêtes. (“Too many requests.”)Too many refused requests from your address (more than 60 per minute).
429queue_full20 runs are already waiting for this routine.
503Service momentanément indisponible. Réessayez dans un instant. (“Service temporarily unavailable. Try again in a moment.”)Temporary outage.

Sample bodies:

404 Not Found
{"detail": "Introuvable."}
429 Too Many Requests
{"detail": {"code": "rate_limited", "message": "Limite de ce déclencheur atteinte."}}

The same 404 is returned for every cause listed, so that nothing reveals whether a trigger exists. Check the address, the key and the signature computation first.

Resend a requestLink to the Resend a request section

  • A request refused with 429, 403 or 503 does not count toward the trigger’s limits. You can resend it as is while its timestamp is less than 5 minutes old, or sign it again with a recent time.
  • Two identical requests (same body, same second) have the same signature: the second one gets 409. Add a unique event ID to the body, for example "id": "evt_123".
  • An accepted request is never run twice, even if it is resent.

What the routine doesLink to the What the routine does section

The routine receives its prompt, followed by the request body presented as data sent by an outside service. The AI uses it as material for the task, without following any instructions it might contain. The body goes through Moon Blur like the rest of the request.

A run started by a webhook:

  • never acts in your connectors (no email sent, no message posted), even if the routine has the “Allow actions” option;
  • does not open web pages, run code, or generate images or videos;
  • does not create a public link to your files or attach any to a message;
  • can read your connectors and search the web;
  • is not retried automatically after an error;
  • counts toward your usage limit like a regular run.

The prompt and the answer are marked as containing outside content: nothing remote loads in the answer until you click. As long as this message stays in the conversation, the following turns keep the same precautions: every action in your tools needs your approval.

Manage triggersLink to the Manage triggers section

ActionEffect
“Enable” followed by the name (toggle)When disabled, the trigger returns 404.
“Rename”Changes the displayed name.
“Renew the signing key”Issues a new key. The old one stops working immediately.
“Delete”Requests sent to its address are refused. This cannot be undone.

Disabling a trigger, renewing its key or deleting it also cancels its queued runs that have not started yet. The list shows, for each trigger, the “Calls received” and the date of the last call. Deleting the routine deletes its triggers.

See alsoLink to the See also section

Moon AI

Moon AI in action

0:00

Explore

Screenshots of chat.realmoon.ai taken on October 29, 2026, with fictional data.

Moon AI

Your preferences

Visits counted without cookies.

Our server counts visits without cookies or trackers. Your theme and your answer about ad measurement are remembered on this device.

Ad measurement

With your consent, our ad networks’ trackers (Google) measure our ads, from the visit to the sign-up and then to the first subscription and its amount, and may show you our ads. Your answer is valid for 13 months; you can change it here at any time.

No answer saved on this device.

After you decline, no tag is loaded anymore and the site deletes the cookies they had set on realmoon.ai. Those on the ad networks’ own domains are managed with them or in your browser.

The Moon AI app has its own privacy settings.