my-webhook-api
$
npx mdskill add myapihq/myapi/my-webhook-apiAccepts inbound webhooks from external services and stores payloads durably.
- Ingest POST payloads from Stripe, GitHub, or any custom service.
- Relies on per-org HTTP endpoints and durable storage.
- Stores every delivery verbatim with headers and timestamp, no schema enforced.
- Returns stored payloads on request; pair with myworkflowapi for reactions.
SKILL.md
.github/skills/my-webhook-apiView on GitHub ↗
---
name: my-webhook-api
version: 1.0.0
description: >
Inbound webhook endpoints for non-funnel sources — Stripe, GitHub, custom services. Funnel forms use the my-funnel-api proxy.
triggers: [webhook, inbound, receiver, stripe events, github webhook, slack notification, delivery, payload, event ingest]
checksum: sha256-pending
---
# MyWebhookAPI
Per-org HTTP endpoints that accept inbound POSTs and durably store every delivery. A webhook on its own is a passive receiver — pair it with **myworkflowapi** to react to incoming events.
## Capabilities
<!-- llm:start -->
**Scope: non-funnel inbound.** Use this for Stripe → MyAPI, GitHub → MyAPI, custom services → MyAPI, anything where an *external* system POSTs to you. **Funnel forms use the funnel proxy** (see `my-funnel-api`: `myapi funnel form ... --capture-to webhook:<id>` if you need to bind a specific destination; otherwise the funnel's auto-provisioned webhook handles it). Don't hardcode `api.mywebhookapi.com/webhook/in/<slug>` URLs into funnel HTML.
Each endpoint has a unique inbound URL minted at create time. Anyone who knows the URL can POST to it; the body is stored verbatim along with headers and a timestamp. Deliveries are kept indefinitely (until you delete the endpoint).
The inbound URL accepts **any JSON body** — no enforced schema, no required fields. Whatever you POST is what gets stored. Two consequences:
1. **You decide the shape.** Form submission, Stripe event, custom payload — all work. Be consistent within one webhook so downstream workflows can reference fields reliably.
2. **Workflows reference fields with `{{ payload.field }}` templating.** If your form POSTs `{"email":"[email protected]","message":"hi"}`, a workflow step can do `--to "{{ payload.email }}"` to send a reply.
Treat the webhook slug as a secret — anyone with the URL can POST.
<!-- llm:end -->
## Commands
<!-- generated:start -->
| Command | What it does |
|---|---|
| `myapi webhook create "<name>" [--description "..."]` | Create an endpoint, returns id + inbound URL |
| `myapi webhook list` | List all endpoints in your org |
| `myapi webhook delete <id>` | Delete an endpoint and all its deliveries |
| `myapi webhook delivery <delivery_id>` | Get the full payload + headers for one delivery |
<!-- generated:end -->
## Examples
<!-- llm:start -->
```bash
# 1. Create a webhook
myapi webhook create "stripe-events" --description "Stripe payment events"
# → prints: Inbound URL: https://api.mywebhookapi.com/webhook/in/<slug>
# 2. POST to it from your service
curl -X POST https://api.mywebhookapi.com/webhook/in/<slug> \
-H "Content-Type: application/json" \
-d '{"event":"payment.succeeded"}'
# → responds with {"delivery_id":"<uuid>"} — capture this to inspect later
# 3. Inspect a delivery by id (there is no list-deliveries route)
myapi webhook delivery <delivery_id>
```
### End-to-end recipe — contact form on your site
A contact form on yoursite.com that sends a thank-you email and a Slack ping when submitted:
```bash
WID=$(myapi webhook create "contact-form" --json | jq -r .id)
WURL=$(myapi webhook list --json | jq -r ".[] | select(.id==\"$WID\") | .url")
myapi workflow create "Contact form handler" \
--endpoint-id $WID \
--steps '[
{"type":"send_email","from":"[email protected]","to":"{{ payload.email }}","subject":"Thanks, {{ payload.name }}!","body":"We got your message."},
{"type":"slack","webhook_url":"https://hooks.slack.com/...","text":"New form submission from {{ payload.name }}"}
]'
# Drop this in your HTML — POSTs JSON to $WURL
echo "<form id='c'>
<input name='name' required>
<input name='email' type='email' required>
<textarea name='message' required></textarea>
<button>Send</button>
</form>
<script>
document.getElementById('c').onsubmit = async e => {
e.preventDefault();
await fetch('$WURL', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(Object.fromEntries(new FormData(e.target)))});
alert('Thanks!');
};
</script>"
```
The form sends `{name, email, message}` → webhook stores it → workflow runs both steps → email + Slack ping.
<!-- llm:end -->
## Notes
- The inbound URL is public — anyone with it can POST. Treat the slug as a secret.
- Deliveries are kept indefinitely. Delete the endpoint to purge them. Look before you delete: `myapi webhook list` first, pass `--org` explicitly; delete verbs require `--yes` in non-interactive runs.
- Failed workflow runs don't affect the delivery record — the inbound POST is always saved.
- Inbound responses: `200` on success, `4xx` if the endpoint is missing or body isn't valid JSON.
Run `myapi webhook --help` for full flag reference.