my-webhook-api

$npx mdskill add myapihq/myapi/my-webhook-api

Accepts 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.

More from myapihq/myapi