Payment Webhook Receiver

Pro feature. Available in Listora Pro.

Availability: Pro only. Requires Listora Pro. Accept payment-completed webhooks from Stripe, PayPal, Paddle, or any custom payment processor and convert them into credits on the user's balance - payment-gateway-agnostic by design. Strict HMAC verification (with timestamp and single-use signature replay protection) is on by default. A payload must be HMAC-verified and replay-protected before it credits anything.

Payment Webhooks - settings tab showing endpoint URL, secret, and last-received log

What it is

Listora doesn't ship a payment gateway - that's a deliberate architectural choice (see Credits & Pricing Plans (Pro)). Instead, the plugin exposes a single hardened webhook endpoint and lets your payment processor of choice POST to it whenever a payment completes. The receiver converts the payment into credits on the user's balance.

Why payment-gateway-agnostic:

  • Sites that use Stripe Checkout point Stripe at the webhook URL.
  • Sites that use WooCommerce checkout flow use the Pro's bundled SDK adapters.
  • Sites that use Paddle / Gumroad / Lemon Squeezy / a custom gateway just need to POST a tiny JSON payload + an HMAC signature.

Security model (the non-negotiable part):

  • Strict HMAC mode is the default (wb_listora_pro_webhook_strict_hmac option, on by default). The receiver requires:
    • X-Listora-Timestamp - the current Unix time in seconds. A request more than 5 minutes off server time is refused.
    • X-Listora-Signature - sha256= followed by the hex HMAC-SHA256 of the timestamp, a dot and the raw body (timestamp.body), computed with the shared secret.
    • A signature can be used once. A repeat of an already used signature is refused as a replay.
  • Legacy fallback - strict mode can be switched off for old gateways with the option or the wb_listora_pro_webhook_strict_hmac filter. The receiver then also accepts the shared secret in an X-Listora-Webhook-Secret header, or a sha256= HMAC of the body alone, without the freshness and replay checks. There is no screen for this setting. It is discouraged.
  • Refused requests are recorded in the Audit Log as Webhook request refused, with the reason.

What the endpoint does on a verified payment:

  1. Calls the Credits SDK Credits::topup($user_id, $amount, $context) - idempotent on gateway_payment_id. Since 1.1.0 the event is recorded before the credit is granted, a replayed event is ignored, and a webhook arriving without a transaction id can no longer double-credit - so gateway retries are always safe.
  2. Fires wb_listora_pro_payment_received action + the canonical wb_listora_pro_credits_added event (via the SDK bridge).
  3. Auto-resumes any of the user's listings currently in listora_payment ("Awaiting Credits") status whose plan cost is now covered.

What the endpoint does on a verified refund (since 1.1.0):

  1. Deducts the real refunded amount carried on the refund event (partial refunds deduct only what was returned), not a flat reversal.
  2. PayPal refunds link back to the original transaction so the ledger reconciles.
  3. If a refund arrives after the paid plan has already activated, the plan is rolled back so the listing's status matches the now-reduced balance. The listings those credits paid for are taken offline and their plan charge is returned. Only listings bought with the refunded credits are reversed. See Credits and Plans.

How you use it

As a site owner - set up an integration

  1. Enable the feature: Listora → Settings → Features → Credit System / Webhook Receiver (always-on infrastructure; on by default).
  2. Open Listora → Settings → Credits → Payments. Copy the Webhook URL (https://yoursite.com/wp-json/listora/v1/webhooks/payment) and the Webhook Secret.
  3. Configure your payment processor:
  • Stripe - Stripe Dashboard → Developers → Webhooks → Add endpoint → URL = your webhook URL → events = checkout.session.completed + payment_intent.succeeded. Stripe's signing secret is separate; the bridge between Stripe's signature and Listora's signature is built into the receiver.
  • Paddle - Paddle Dashboard → Developer Tools → Notifications → Add → URL = your webhook URL → events = transaction.completed.
  • Custom processor - POST JSON to the URL with X-Listora-Timestamp: <unix seconds> and X-Listora-Signature: sha256=<hex HMAC-SHA256 of "<timestamp>.<body>" with the secret>.
  1. Test: make a small test payment and check that the member's balance went up in Listora → Monetization → Transactions.

Payload shape (custom processor)

POST /wp-json/listora/v1/webhooks/payment
Content-Type: application/json
X-Listora-Timestamp: 1716232847
X-Listora-Signature: sha256=5e3a7c…

{
"event": "payment.completed",
"gateway": "your-gateway-id",
"gateway_payment_id": "ch_3OZ…", // idempotency key
"user_id": 42,
"user_email": "owner@example.com",
"amount_credits": 100,
"amount_currency": 19.99,
"currency": "USD",
"meta": { ... }
}

Settings & options

Setting Location Default Notes
Endpoint POST /wp-json/listora/v1/webhooks/payment Always registered Public-write endpoint, HMAC-gated
Strict HMAC wb_listora_pro_webhook_strict_hmac option or filter On No settings screen. Disable only to support legacy integrations.
Webhook secret Listora → Settings → Credits → Payments Generated on activation Changing it invalidates existing integrations
Idempotency window (system) Per-gateway_payment_id Same payment ID never credited twice
Replay protection Timestamp (5 minutes) plus single-use signature (system) Both must pass. The window can be changed with the wb_listora_pro_webhook_max_age filter.
Log Audit Log (Pro) - Every refused request is recorded

Developer hooks:

  • wb_listora_pro_payment_received (action, 4 args) - fires after credits land. Listeners email, push to CRM, notify Slack.
  • wb_listora_pro_webhook_strict_hmac (option / filter) - programmatic override of strict-mode (advisable: never disable in production).
  • wb_listora_pro_webhook_payload_normalized (filter) - modify the parsed payload before crediting (e.g. apply gateway-specific currency conversion).
  • wb_listora_pro_webhook_verification_failed (action) - listen for rejected webhooks for monitoring.

Tax / VAT / GST: the webhook receiver credits a flat amount and does not calculate VAT/GST or issue invoices. If you owe consumption tax on credit sales (EU/UK VAT, AU/NZ GST, etc.), sell credits through WooCommerce instead, where your store handles location-based tax + invoices. See Tax, VAT and GST compliance.

Related