Skip to main content

Listener Support Donations REST API

REST endpoints for recording and retrieving listener donation references. These endpoints are part of the benecaster/v1 namespace and live on the main yoursite.com WordPress installation — not on updates.benecaster.com.


POST /benecaster/v1/listener-support/donations

Records a donation reference after a listener has donated on an external platform (Ko-fi, PayPal, Buy Me a Coffee, etc.).

Auth: Public — no nonce required. This is intentional: external platforms (Ko-fi, PayPal, Buy Me a Coffee) send webhook-style callbacks without WordPress credentials, so requiring authentication would block them. Cookie authentication is optional: when a logged-in user submits, their user_id is stored with the row; anonymous submissions store user_id = NULL.

Security note: Because this endpoint is public, anyone who knows a valid show_id can submit a fabricated donation reference. This does not represent financial risk — no money changes hands through this endpoint — but it could result in fake records in your donation log and unwanted thank-you emails sent to submitted addresses. Two things stand in front of it. At most five unverified submissions per hour are accepted from any one address; the sixth gets 429. A webhook that passes its platform’s signature check is exempt, so a busy show’s signed webhooks are never turned away. And a site owner or add-on can refuse a submission on its own rules with the benecaster_listener_support_accept_donation filter before anything is written. There is no switch to turn the endpoint off. For verified, fraud-resistant donations, use Stripe mode instead — Stripe payments are server-verified and cannot be fabricated.

Request body:

{
  "show_id":     1,
  "platform":    "ko-fi",
  "donor_email": "listener@example.com",
  "amount":      12.50,
  "currency":    "USD",
  "reference":   "TX-123ABC",
  "note":        "Love the show, keep it going!",
  "donated_at":  "2026-06-13T14:30:00Z"
}

A PayPal webhook sends PayPal’s own event instead. PayPal’s event body has no show_id or platform field, so both are read from the query string of the webhook URL (…/listener-support/donations?show_id=12&platform=paypal); a value in the body still wins. Only a completed payment (Payment capture completed, or Payment sale completed from an older button) is recorded, with its amount, currency and PayPal transaction ID as reference. PayPal’s payment events carry no donor email, so no thank-you email is sent for them. See Webhook Signature Verification for the setup.

Field reference:

Field Required Type Notes
show_id Yes int Must reference a published benecaster_show post.
platform Yes string Platform slug, e.g. 'ko-fi', 'paypal', 'buymeacoffee'. No validation against a fixed list — any non-empty string is accepted.
donor_email No string Sanitized via sanitize_email + is_email. Stored null when absent or invalid.
amount No number Non-negative. Stored null when absent.
currency No string ISO 4217 code; uppercased before storage (e.g. "usd" → "USD").
reference No string Platform transaction ID or order number; max 255 characters.
note No string Supporter-supplied message.
donated_at No string Datetime the donation was made. Send an offset — 2026-06-13T14:30:00Z or 2026-06-13T10:30:00-04:00. A value carrying an offset is honoured and converted to UTC. A naive value (no offset) is taken as UTC as-is, not as your site’s local time. Absent or unparseable defaults to UTC now.

Success response — HTTP 201:

{
  "id":          42,
  "show_id":     1,
  "user_id":     17,
  "platform":    "ko-fi",
  "donated_at":  "2026-06-13 14:30:00",
  "row": { }
}

user_id is null for anonymous submissions. row contains the full recorded donation, in the same shape as an entry in the GET response below.

donated_at is stored and returned in UTC, as a MySQL datetime string (2026-06-13 14:30:00) rather than in the ISO 8601 form the request accepts. Convert to your reader’s timezone at the display boundary; do not assume the site’s timezone setting has already been applied.

After the row is written, the benecaster_listener_support_donation_logged action fires synchronously. If the submitted donor_email is a valid address, a donation_thank_you email is queued for that address.

Two other answers, both HTTP 200, for a PayPal event. Neither writes a row or fires benecaster_listener_support_donation_logged:

  • { "ignored": true, "event_type": "…" } — an event that isn’t a completed payment.
  • { "id": 42, "show_id": 1, "duplicate": true } — a PayPal transaction already recorded for this show. PayPal retries deliveries, so the same payment is never recorded twice.

Error responses:

HTTP Code Condition
400 — show_id or platform is missing from both the body and the query string.
403 signature_invalid A webhook secret is configured for this platform and the incoming signature or token failed verification. Body: { "code": "signature_invalid", "message": "Webhook signature verification failed." }
403 donation_rejected The benecaster_listener_support_accept_donation filter refused the submission. Nothing is written.
404 — show_id does not match a published benecaster_show post.
429 benecaster_rate_limited More than five unverified submissions from this address in the past hour. Carries a Retry-After header. A verified webhook is never limited.
500 — Database insert failure.

GET /benecaster/v1/listener-support/donations

Returns a paginated list of logged donation references.

Auth: manage_options + X-WP-Nonce

Query parameters:

Parameter Default Notes
show_id (none) Filter to a single show. Omit for the site-wide list.
page 1 Page number.
per_page 20 Results per page. Clamped to a maximum of 100.

Response:

{
  "items": [
    {
      "id":         42,
      "show_id":    1,
      "user_id":    17,
      "platform":   "ko-fi",
      "amount":     12.50,
      "currency":   "USD",
      "reference":  "TX-123ABC",
      "note":       "Love the show, keep it going!",
      "donated_at": "2026-06-13 14:30:00"
    }
  ],
  "total":    156,
  "page":     1,
  "per_page": 20
}

Rows are ordered by donated_at DESC, id DESC. donated_at is UTC, returned as a MySQL datetime string. user_id is null for anonymous submissions. amount, currency, reference, and note are null when not provided at submission time.

Use cases:

  • Building a donor board or “thank-you wall” on your site — pull the list server-side and render it in a template.
  • Syncing donation references to a CRM or analytics platform — poll this endpoint periodically or use the benecaster_listener_support_donation_logged action for real-time sync.
  • Displaying a running donation total — aggregate amount values on the client or in a server-side cron.

Webhook Signature Verification

When a webhook secret is configured for a platform via DonationWebhookSecrets (see Settings → Listener Support), this endpoint verifies the incoming request’s signature or token before writing the donation row.

Per-platform verification mechanism:

Platform Header inspected Verification method
Ko-fi X-KoFi-Token Constant-time string compare against stored token
PayPal PAYPAL-TRANSMISSION-ID, -TIME, -SIG, PAYPAL-CERT-URL, PAYPAL-AUTH-ALGO RSA-SHA256 signature over the transmission ID, time, your webhook ID and a checksum of the body, checked against PayPal’s certificate. The certificate is fetched only from PayPal’s own API addresses.
Buy Me a Coffee X-BMAC-Token Constant-time string compare against stored secret

Behavior when a secret is configured:

Behavior when no secret is configured for a platform: Requests are accepted as before — no verification runs, no action fires. Verification is strictly opt-in.

Platform slug normalization: The platform field is normalized before the secret lookup — Ko-Fi, ko_fi, kofi, and KoFi all resolve to the canonical kofi slug. The raw value is preserved in the logged row.

Stripe bypass: Requests with platform=stripe are rejected at this endpoint (Stripe payments process through /stripe-webhook). No verification runs for them here.

See Webhook Signature Verification → for the setup walkthrough and full behavior reference.


Donation Verification

A hand-reported donation reference is recorded as submitted — this endpoint does not check that the payment actually happened on the external platform, and many podcasters use platforms with no API to check against. A PayPal webhook is different: with your webhook ID saved, its signature proves PayPal sent it.

If you need verified donations (amount confirmed, fraud-resistant), use Stripe mode instead. Stripe mode processes payments directly and records verified transactions automatically.

See Also

Need this built rather than just documented? See our services →