API Prompt
Natural-language HTTP trigger for AI-first workflows.
The API Prompt trigger exposes an HTTP endpoint that accepts a natural-language prompt (plus optional structured context and metadata) and runs a workflow with those fields available as block outputs. It's the right trigger when you want your API to feel like "chat with a workflow" rather than "fill out this JSON schema."
Request contract
POST /api/webhooks/prompt/{path}
Content-Type: application/json
{
"prompt": "Summarize these PDFs and email me the result",
"context": { "pdf_ids": ["abc", "def"] },
"metadata": { "request_origin": "internal-app-v2" }
}prompt(string, required) — natural-language instruction. Capped atmaxPromptLength(default 10 000 chars, hard limit 50 000).context(object, optional) — arbitrary JSON. Use it to pass structured references (IDs, file paths, user profile) alongside the prompt.metadata(object, optional) — caller-supplied identifiers you want surfaced for routing/logging.
Any additional top-level fields are ignored. Nested JSON inside context and metadata is preserved.
Block outputs
Available in downstream blocks via <apiprompt1.*>:
| Output | Type | Description |
|---|---|---|
prompt | string | The caller's prompt, trimmed + length-validated |
context | json | null | The context object, or null if not provided |
metadata | json | null | The metadata object, or null if not provided |
requestId | string | Server-generated ID — propagates into logs/traces |
caller | string | Derived caller identity (bearer-hash / header-hash / hmac-hash / IP) |
timestamp | string | ISO-8601 server receive time |
guardrailsTriggered | boolean | True if any known prompt-injection pattern matched |
matchedInjectionPatterns | array | Names of patterns that matched (empty if none) |
Authentication
Supports the same 4 modes as the generic Webhook trigger:
| Mode | Header sent | Good for |
|---|---|---|
none | — | Public endpoints, internal tools behind another gateway |
bearer | Authorization: Bearer <token> | Programmatic AI callers — recommended default |
custom_header | <configured-name>: <token> | Matching legacy conventions |
hmac_sha256 | X-Signature: <hex> over raw body | Maximum assurance — stateless, replay-protected |
See Webhooks → Authentication for signing examples in bash / Node / Python.
Calling from a consumer — HMAC quickstart
When you configure HMAC-SHA256 authentication, the platform expects a hex digest of
the exact raw bytes of your request body, signed with your shared secret, sent in the
X-Signature header (or whatever name you set under Signature header).
The signature must cover the bytes you actually send on the wire. If your HTTP client
re-serializes the JSON (re-orders keys, drops whitespace, escapes Unicode differently),
the signature will not match. Build the body string once, sign that string, send that
string. In curl, use --data-raw. In Python, pre-encode with json.dumps(...).encode()
and pass data=body. In Node, build the string and pass it as the body of fetch.
curl
WEBHOOK_URL="https://mybotbox.com/api/webhooks/prompt/<path>"
SECRET="<your HMAC secret>"
BODY='{"prompt":"Summarize last week of standup notes","context":{"team":"platform"}}'
TIMESTAMP=$(date +%s)
SIGNATURE=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')
curl -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-H "X-Signature: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP" \
--data-raw "$BODY"Notes:
X-Timestamp(Unix seconds, or ISO-8601) is required when the trigger's replay window is on (default 300s). Without it you get401 — timestamp outside replay window.sha256=prefix is accepted but not required — the server strips it case-insensitively.X-Idempotency-Key: <your-request-id>is optional but recommended so retries return200 { "status": "duplicate" }instead of re-firing the workflow.
Python
import hmac, hashlib, json, time, requests
WEBHOOK_URL = "https://mybotbox.com/api/webhooks/prompt/<path>"
SECRET = b"<your HMAC secret>"
body = json.dumps(
{"prompt": "Summarize last week of standup notes", "context": {"team": "platform"}},
separators=(",", ":"),
).encode()
sig = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
resp = requests.post(
WEBHOOK_URL,
data=body,
headers={
"Content-Type": "application/json",
"X-Signature": sig,
"X-Timestamp": str(int(time.time())),
"X-Idempotency-Key": "req-2026-05-17-001",
},
)
print(resp.status_code, resp.text)Node.js / TypeScript
import { createHmac } from 'node:crypto'
const WEBHOOK_URL = 'https://mybotbox.com/api/webhooks/prompt/<path>'
const SECRET = process.env.MBB_HMAC_SECRET! // never hardcode
const body = JSON.stringify({
prompt: 'Summarize last week of standup notes',
context: { team: 'platform' },
})
const sig = createHmac('sha256', SECRET).update(body).digest('hex')
const res = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Signature': sig,
'X-Timestamp': Math.floor(Date.now() / 1000).toString(),
},
body,
})
console.log(res.status, await res.text())Onboarding a consumer
When you give a third party access to one of your API Prompt triggers, share exactly four things — and nothing else:
- Webhook URL —
https://mybotbox.com/api/webhooks/prompt/<path> - HMAC secret — the value you typed into the "HMAC secret" field
- Signature header name —
X-Signature(default) or whatever custom name you set - Body contract — the shape
{ prompt: string, context?: object, metadata?: object }, plus your configuredmaxPromptLengthand whetherrequireContextis on
How to share the secret safely
The HMAC secret authenticates every call. If it leaks, anyone can fire your workflow with arbitrary prompts. Treat it like a database password.
Don't:
- Email or Slack it in plain text
- Paste it into shared docs (Notion, Confluence, Google Docs)
- Commit it to any repo, even private
- Reuse the same secret across staging and production
Do, in order of preference:
- 1Password / Bitwarden shared item — scoped to the recipient's email, with an expiry. Most teams already have this.
- Encrypted message — Signal, or
gpg --encrypt --recipient <them@…>then send the ciphertext over any channel. - One-time-secret link —
https://onetimesecret.comor a self-hosted equivalent. Single-view, auto-destruct after a few minutes. - Read it aloud on a video call — the recipient pastes it into their own secret store live, then you both close the share. Nothing written down.
Operational hygiene
- Generate a strong secret:
openssl rand -hex 32(256 bits). The1234567890-style placeholders are fine for the first smoke test, but rotate before sharing externally. - Rotate every 90 days, or immediately if a consumer offboards. Update the secret in the trigger UI, save, then notify your consumer. Old signatures stop working at the moment you save — if you need a grace window, run two triggers in parallel during the cutover.
- Tell consumers where to put it: their secret manager (AWS Secrets Manager / GCP Secret Manager / HashiCorp Vault / Doppler) or their CI env-var store. Never in source.
- Watch the logs after rotation: a sudden burst of 401s usually means a consumer missed the rotation memo.
The URL the trigger UI displays today is the canonical endpoint:
/api/webhooks/prompt/<path>. The older /api/webhooks/trigger/<path> form (which the
UI emitted for every trigger type in earlier versions) still works for back-compat — both
routes share the same HMAC verification + safeguards. Prefer the /prompt/ form when
documenting integrations.
Replay protection, idempotency, and rate limiting
- Replay window (HMAC mode only): reject requests where
X-Timestampis more than N seconds from server time. Default 300s,0disables. - Idempotency: when enabled, duplicate requests (matched on
X-Idempotency-Key, bodyid, or body hash) return200 { status: "duplicate" }without re-running the workflow. 7-day dedup window. - Per-caller rate limit: default 20 requests/min, keyed by the derived caller identity. Triggers
429withRetry-Afterheader when exceeded.
Prompt-injection guardrails
When enabled (default on), the platform scans the prompt against a small deny-list of common injection patterns (ignore previous instructions, system: prefix, chat-template markers, etc.) and surfaces the result as guardrailsTriggered + matchedInjectionPatterns. The guardrails are non-blocking — the workflow still runs — so you can decide per workflow whether to refuse, route to human review, or proceed.
Guardrails are a best-effort prefilter, not a replacement for LLM-side defenses. Always treat the prompt as untrusted input in downstream blocks. For high-risk operations (sending email, executing code, calling paid APIs), add explicit confirmation steps or route to review when guardrailsTriggered is true.
Typical workflow shape
- API Prompt trigger (this block) — caller posts
{ prompt, context }. - Agent / LLM block — receives
<apiprompt1.prompt>and<apiprompt1.context>, plans next actions using tool-calling. - Downstream action blocks (email, database, third-party APIs) — execute the plan.
- Response block — returns the agent's summary to the original caller.
Example — PDF summarizer
curl -X POST https://mybotbox.com/api/webhooks/prompt/abc123 \
-H 'Authorization: Bearer $MBB_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"prompt": "Summarize these PDFs into bullet points and email to team@example.com",
"context": {
"pdf_gcs_paths": [
"gs://my-bucket/docs/report-2026-q1.pdf",
"gs://my-bucket/docs/report-2026-q2.pdf"
]
}
}'Workflow setup:
- API Prompt block → Agent (Claude Haiku) block with tools
[gcs_read_file, gmail_send]and system prompt"Follow the user's instructions. Use tools as needed.". - Agent's user message:
<apiprompt1.prompt>\n\nContext: <apiprompt1.context>.
Verify with the bundled scripts
# Bearer token mode (recommended default).
bun apps/sat/tests/staging/triggers/api_prompt/verify.ts \
https://mybotbox.com/api/webhooks/prompt/<path> \
--secret=<bearer-token>
# Or HMAC mode.
bun apps/sat/tests/staging/triggers/api_prompt/verify.ts \
https://mybotbox.com/api/webhooks/prompt/<path> \
--hmac=<hmac-secret>python3 apps/sat/tests/staging/triggers/api_prompt/verify.py \
https://mybotbox.com/api/webhooks/prompt/<path> \
--secret=<bearer-token>Source: apps/sat/tests/staging/triggers/api_prompt/.
When to use API Prompt vs Generic Webhook
| Use API Prompt when… | Use Generic Webhook when… |
|---|---|
| Body is a prompt + context | Body is structured JSON with fixed fields |
| First block is an LLM/agent that plans | First block is a Condition / Database action |
| Caller is an AI system or chatbot | Caller is a service posting events |
| You want guardrails + per-caller rate limits out of the box | You want provider-style HMAC + dedupe |
Generic Webhook is simpler and faster when the body fields map 1:1 to workflow variables. Use API Prompt when you want the LLM to be the router.