Appearance
Inbound webhooks
Services you already use can post their webhooks to simpledatalogs. Each delivery becomes one event in your environment, so "someone bought me a coffee" or "a checkout completed" shows up next to your app's own events.
Webhooks are free on every plan. Each delivery that becomes an event counts toward your monthly quota.
Add a source
- Open your environment, then Webhooks → New source.
- Pick the service, name it, and pick the deployment its events belong to.
- Paste the service's signing secret (where to find it is below). You can add it later under Edit.
- Click Create source and copy the URL. It's shown once:
text
https://edge.simpledatalogs.com/v1/hooks/sdl_hook_…- Paste the URL into the service.
The source shows its last delivery and any refused ones. New URL replaces the URL; deliveries to the old one are refused for a week and listed as "Old URL used". Delete stops the URL. Events already stored stay either way.
Owners and members manage sources. Viewers see the events but never the URLs or secrets.
What every event gets
| Field | Value |
|---|---|
| Name | The service's event type, lower case, with ., : and spaces as _: donation.created → donation_created |
event_id | From the service's own event id, so a retried delivery is stored once |
ts | The service's own time for the event |
user_id | The payer's or respondent's email, stored as an email: pseudonym unless your project keeps emails readable |
props.webhook | The service, e.g. stripe |
| Other props | Amount (in the currency's own unit, not cents), currency, status, product, plan, country and so on, per service |
Names, addresses, messages, form answers, IP addresses and card details are never stored.
With a secret pasted, a delivery without a valid signature is refused with 401, and the service retries or shows the failure. Without one, the URL alone authenticates, so keep it private.
Buy Me a Coffee
Settings → Webhooks → Add webhook. Paste the URL, pick the events, and copy its signing secret into the source.
| Event | Name | Props |
|---|---|---|
donation.created | donation_created | amount, currency, status, coffee_count, support_type |
membership.started | membership_started | amount, currency, plan, interval, status |
extra_purchase.created | extra_purchase_created | amount, currency, product |
Every other type (refunds, commissions, wishlist, recurring donations) is named the same way. Test events have test: true.
Stripe
Developers → Webhooks → Add destination. Paste the URL, pick the events, and copy the whsec_… signing secret.
| Event | Name | Props |
|---|---|---|
checkout.session.completed | checkout_session_completed | amount, currency, status, mode, country |
invoice.paid | invoice_paid | amount, currency, status, plan, interval |
customer.subscription.created | customer_subscription_created | status, plan, interval |
Signatures older than 5 minutes are refused, so a captured delivery can't be replayed. Test-mode events have test: true.
Lemon Squeezy
Settings → Webhooks → +. Paste the URL, type a signing secret of your own, and paste the same secret into the source.
order_created, subscription_created, subscription_payment_success and the rest keep their names; props are amount, currency, status, product, plan (the variant) and test.
Paddle
Developer tools → Notifications → New destination. Paste the URL, pick the events, and copy the destination's secret key.
transaction.completed becomes transaction_completed, subscription.created becomes subscription_created, and so on; props are amount (the grand total), currency, status, product and interval. Signatures older than 5 minutes are refused.
Gumroad
Settings → Advanced → Ping. Paste the URL and save. Gumroad doesn't sign pings, so the URL is the secret.
Each sale is a sale event (refund when the ping says it was refunded) with amount, currency, product, quantity, plan (the recurrence), country and test. Card details in the ping are dropped.
Ko-fi
More → API → Webhooks. Paste the URL, then open Advanced and copy the verification token into the source.
Types become donation, subscription, commission and shop_order, with amount, currency, plan (the tier) and the subscription flags.
Patreon
Creator portal → My webhooks. Paste the URL, pick triggers, and copy the webhook's secret.
Triggers become names: members:pledge:create → members_pledge_create. Props are amount (the entitled amount), status, charge_status, plan (the tier) and interval_months. Patreon sends no event id, so the id comes from the member and its last charge.
GitHub
Repository (or organization) → Settings → Webhooks → Add webhook. Paste the URL, choose content type application/json, and type a secret there and in the source.
The name is the event plus its action: issues_opened, pull_request_closed, star_created, push, release_published. Props are repository, action, ref, number, state, merged, conclusion, tag, commits and stars. GitHub's ping marks the source as delivered but isn't an event.
Typeform
Form → Connect → Webhooks. Add the URL, then Edit → Secret and type a secret there and in the source.
Each submission is a form_response event with form_id, form (its title), answers (how many) and score. Answers themselves are never stored; an email answer becomes the user_id pseudonym.
Tally
Form → Integrations → Webhooks. Paste the URL and add a signing secret, then paste the same secret into the source.
Each submission is a form_response event with form_id, form and fields (how many). Field values are never stored; an email field becomes the user_id pseudonym.
Any other service
Pick Other (JSON) for anything that POSTs a JSON object.
| Field | Default | Set it when |
|---|---|---|
| Name | event, type, event_type or name | The name lives elsewhere, e.g. data.kind |
| ID | id, event_id or uuid | Retries should dedupe on another field |
| Time | ts, timestamp, created_at or time | The time lives elsewhere |
Fields are dotted paths into the body (data.object.id; a number picks a list item). Other values become props, flattened two levels deep (service.region), up to 50; lists and keys that look personal (email, name, phone, address, ip …) are left out.
Signed services work when they use either:
- Standard Webhooks (
webhook-id,webhook-timestamp,webhook-signatureheaders; Svix, Resend, Clerk and others): paste thewhsec_…secret. - An HMAC-SHA256 of the body in
X-Signature,X-Webhook-SignatureorX-Hub-Signature-256, as hex or base64, with or withoutsha256=.
A GET with ?challenge= (or ?hub.challenge=) to the URL echoes the challenge, for services that verify the URL first.
Answers
| Status | When |
|---|---|
200 {"accepted": "<id>"} | Stored. Only this means delivered |
401 invalid_signature, missing_signature, stale_signature | The secret doesn't match, no signature came, or it's older than 5 minutes |
404 unknown_webhook | No such URL, or a replaced one |
413 | The body is over 512 KB |
429 + Retry-After | Too many deliveries, or the organization is past 200 % of its quota |
503 + Retry-After | Busy; the service retries |
Every service above retries non-200 answers on its own schedule.
Limits
- 512 KB per delivery; GitHub pushes with thousands of commits can be larger and are refused with
413. - 20 sources per environment.
- Each delivery that becomes an event counts toward your quota. Pings and refused deliveries don't.