Build or audit integrations around WooCommerce Stripe Gateway webhooks and asynchronous payment settlement. Covers the canonical wc-api endpoint, Stripe-Signature and connected-account checks, trusted webhook health state, order resolution, PaymentIntent and Checkout Session deferral, settlement amount/currency integrity, Action Scheduler, order locks, idempotency, safe observer hooks, Adaptive Pricing opt-out, unexpected charges, logging, and deprecated Stripe surfaces. Use for `wc_stripe_we...
Scanned 9/5/2026
Install to Claude Code
npx -y skills add Lonsdale201/wp-agent-skills --skill wc-stripe-webhooks --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Wc Stripe Webhooks?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/lonsdale201-wc-stripe-webhooks)More formats (shields.io, HTML) on the badges page.
---
name: wc-stripe-webhooks
description: Build or audit integrations around WooCommerce Stripe Gateway webhooks and asynchronous payment settlement. Covers the canonical wc-api endpoint, Stripe-Signature and connected-account checks, trusted webhook health state, order resolution, PaymentIntent and Checkout Session deferral, settlement amount/currency integrity, Action Scheduler, order locks, idempotency, safe observer hooks, Adaptive Pricing opt-out, unexpected charges, logging, and deprecated Stripe surfaces. Use for `wc_stripe_webhook_received`, `wc_stripe_is_adaptive_pricing_supported`, `payment_intent` or `checkout.session` events, on-hold Stripe orders, duplicate settlement, custom observers, or reconciliation.
metadata:
wp-skills-author: "Soczó Kristóf"
wp-skills-contact: "mailto:lonsdale201@hotmail.com"
wp-skills-plugin: "woocommerce-gateway-stripe"
wp-skills-plugin-version-tested: "10.9.0"
wp-skills-woocommerce-version-tested: "11.0.1"
wp-skills-php-min: "7.4"
wp-skills-last-updated: "2026-08-19"
---
# WooCommerce Stripe webhooks
Use this when custom code observes Stripe events or diagnoses paid orders stuck in Pending/On hold. Let the gateway own signature verification, order matching, locks, deferred processing, and order transitions.
## Endpoint and trust boundary
The plugin endpoint is generated by `WC_Stripe_Helper::get_webhook_url()`:
```text
https://example.com/?wc-api=wc_stripe
```
The handler runs on `woocommerce_api_wc_stripe` and accepts POST only. Do not register a second endpoint for the same Stripe events merely to run business logic.
For normal Stripe events it verifies:
- configured live/test webhook secret
- `Stripe-Signature` shape
- HMAC-SHA256 over `timestamp.raw_body`
- timestamp within five minutes
Never parse `php://input`, trust `metadata.order_id`, or update an order before the installed handler validates the request. Agentic Commerce uses the same URL but a separate secret and event family; do not treat its delegated checkout payload as an ordinary order webhook.
Stripe Gateway 10.9.0 updates the stored pending-webhook count only after signature validation succeeds. Do not write health/status counters from an unverified payload in custom webhook code. Webhook timestamps and pending counts are scalar non-negative integer state; 10.9.0 ignores invalid array/object/non-digit values instead of persisting them.
### Connected-account binding in 10.9
After signature validation, the gateway compares a Connect event's `account` or an agentic delegated-checkout event's `context` with the cached connected Stripe account ID for the active mode. A known mismatch is logged, acknowledged with HTTP 200, and is not processed; it does not update pending/success health state or fire `wc_stripe_webhook_received`.
The check deliberately fails open when the event carries no account/context or the connected account is unknown. Do not describe the account check as a replacement for signature verification, and do not copy the fail-open policy into a custom multi-account endpoint without an explicit compatibility reason. A custom endpoint that knows its expected account should fail closed before order lookup.
## Event families
The gateway handles, among others:
- `payment_intent.processing`, `.succeeded`, `.payment_failed`, `.amount_capturable_updated`, `.requires_action`
- `setup_intent.succeeded`, `.setup_failed`
- `checkout.session.completed`, `.async_payment_succeeded`, `.expired`, `.async_payment_failed`
- charge success/failure/capture/refund/dispute events
- legacy asynchronous Source events
- `account.updated`
Event delivery is not the business event. For example, `payment_intent.processing` can place an order On hold, while `requires_action` is not payment success.
## Order resolution
The handler resolves orders using signed metadata plus stored Stripe identifiers, with fallbacks for intent/session/source data. It verifies the stored intent/signature where possible and uses `wc_get_order()` for HPOS compatibility.
Custom observers must use the resolved `WC_Order|null` passed by the gateway. Do not independently load `metadata.order_id` and assume it belongs to the event.
Stripe order meta includes intent, SetupIntent, Checkout Session, customer, source/payment method, presentment amount/currency, transaction, lock, and status flags. These are implementation details. Use `WC_Order` and gateway helper APIs; never query `wp_postmeta` or copy Stripe intent/lock meta between orders.
## Deferred settlement
Stripe 10.8.3 deliberately defers common successful PaymentIntent processing through Action Scheduler. Checkout Session events are also deferred when the order or its metadata is not yet available. This protects checkout/webhook races and lets the order-received request finish storing identifiers.
Relevant action:
```text
wc_stripe_deferred_webhook
```
Default PaymentIntent deferral is two minutes. A webhook that loses the order-payment lock race can retry after a shorter delay. Do not assume a 200 response means order settlement completed synchronously.
Stripe 10.9 serializes deferred PaymentIntent success and Checkout Session success against the return/3DS path with the order lock. A collision is re-queued rather than dropped, and `wc_stripe_webhook_received` is withheld until the retry actually processes the event. This prevents duplicate transitions while preserving the initial paid transition's stock, notes, and email side effects. The observer still does not guarantee payment success: tolerate delayed execution and inspect the final order state instead of inferring success from the event name or original delivery time.
Do not disable async processing with `wc_stripe_process_payment_intent_webhook_async` merely to make custom code run sooner. If changed, test redirect methods, concurrent checkout, duplicate delivery, and order locks.
## Locks and idempotency
The gateway uses order payment/refund locks and Stripe API idempotency keys. `lock_order_payment()` returns `true` when the order is already locked, not when the caller acquired it; this inverted-looking contract is easy to misuse.
Do not add your own payment attempt on `wc_stripe_webhook_received`. Duplicate Stripe delivery, checkout return handling, and deferred jobs may all represent the same payment. Make custom side effects idempotent with a durable key such as order ID plus transaction ID/event purpose.
For fulfillment/provisioning, prefer Woo's paid-order hooks and verify the order is paid:
```php
add_action( 'woocommerce_payment_complete', function ( int $order_id ): void {
$order = wc_get_order( $order_id );
if ( ! $order || 0 !== strpos( $order->get_payment_method(), 'stripe' ) ) {
return;
}
myplugin_provision_once( $order->get_id(), $order->get_transaction_id() );
} );
```
Use WCS renewal-complete hooks for subscription renewal provisioning.
## Safe webhook observer
`wc_stripe_webhook_received` fires after that event's gateway processing step and receives:
```php
add_action(
'wc_stripe_webhook_received',
function ( string $type, object $event, ?WC_Order $order ): void {
if ( ! $order || 'payment_intent.succeeded' !== $type || ! $order->is_paid() ) {
return;
}
myplugin_record_stripe_observation_once(
$order->get_id(),
(string) ( $event->id ?? '' )
);
},
10,
3
);
```
The order can be null for account events, unmatched events, or non-order flows. The action can also run later from the deferred job. Catch failures in your own integration; the gateway catches thrown `Throwable`, logs it, and still owns the HTTP response.
Do not use this observer as the only fulfillment signal: filter to the exact event, require the expected final order state, and make the side effect idempotent.
## Adaptive Pricing and Checkout Sessions
Adaptive Pricing uses Checkout Sessions and requires healthy webhooks. Stripe 10.8 disables Adaptive Pricing when webhooks are disabled. The gateway distinguishes Adaptive Pricing sessions from agentic sessions via checkout metadata and defers early events until order metadata exists.
Stripe 10.9 exposes `wc_stripe_is_adaptive_pricing_supported( true, WC_Cart|null $cart )` as a final opt-out after its own account, settings, page, subscription, pre-order, and deposit checks pass. Return `false` for a narrowly identified incompatible cart; it cannot force an otherwise unavailable flow on. The older `wc_stripe_is_checkout_sessions_available` filter was removed, so delete integrations that still depend on it.
For custom order fields:
- Store durable Woo data before redirect whenever possible.
- Do not mutate Stripe's `checkout_type` or signature metadata.
- Do not create an order from an unmatched Adaptive Pricing session; the gateway intentionally refuses to route it into agentic order creation.
- Reconcile presentment currency/amount from gateway helpers rather than replacing the Woo order currency/total.
### Settlement amount and currency integrity in 10.9.0
Before marking a Checkout Session paid, the gateway now compares the settlement amount/currency with the Woo order total/currency. It understands both Stripe schemas:
- before the `2025-03-31.basil` API version, settlement values for Adaptive Pricing are under `currency_conversion.source_currency` and `currency_conversion.amount_total`;
- Basil and later, including the gateway's `2026-03-25.dahlia` API version, use top-level `currency` and `amount_total` for settlement, while buyer-facing figures can live in `presentment_details`.
On a missing or mismatched settlement value, the gateway refuses payment completion, writes a diagnostic order note with the Checkout Session and available PaymentIntent reference, and moves the order to `on-hold`. Holding prevents Woo's unpaid-pending cancellation from restoring stock for a payment Stripe may already have captured.
Treat this state as a manual-reconciliation signal. Do not auto-call `payment_complete()`, rewrite the order total/currency, or move the order back to pending merely because Stripe reported `checkout.session.completed`. Resolve the Stripe object, Woo order, API-version schema, and amount conversion first; make any operator remediation explicit and auditable.
## Unexpected charges
Stripe 10.8 detects a captured Stripe charge for an order already paid by another gateway. It writes an order note, deduplicates by PaymentIntent, and fires:
```text
wc_stripe_unexpected_charge_detected
```
Arguments are `WC_Order $order`, Stripe charge object, and webhook type. Use this for alerting/reconciliation, not automatic refunding without an explicit, idempotent business policy.
## Logging and health
Use WooCommerce > Status > Logs and Stripe's webhook/status UI. Correlate Woo order ID, PaymentIntent/Checkout Session/charge ID, event type, and Action Scheduler action.
Stripe 10.8 adds `wc_stripe_logger_can_log` for targeted **enablement** when verbose debug and the global logging setting are both off. It cannot suppress logs already enabled by those settings because the logger returns `true` before applying the filter. The filter receives allowed flag, level, calling class, and calling function and can run frequently, so keep callbacks cheap. Never log webhook secrets, API keys, client secrets, full payloads containing customer data, or card/bank details.
Useful checks:
```bash
wp action-scheduler action list --hook=wc_stripe_deferred_webhook --status=pending
wp option get woocommerce_stripe_settings --format=json
```
Redact secrets before sharing option output.
## Abilities API caveat
Stripe 10.8 contains read-only Stripe abilities, but registration is gated by `wc_stripe_abilities_enabled` and defaults to `false`. It also requires WooCommerce 10.9's `AbilitiesLoader`; on older Woo versions it no-ops. The registrar and ability classes are marked internal. Do not make payment settlement, reconciliation, or production automation depend on this surface until your integration explicitly controls the feature gate and pins the Stripe/Woo versions.
## Deprecated surfaces
- `WC_Gateway_Stripe` is deprecated; use the runtime `WC_Stripe_UPE_Payment_Gateway` only when direct class integration is unavoidable.
- `wc_gateway_stripe_process_payment` is deprecated since 9.7; the replacement is `wc_gateway_stripe_process_payment_charge`.
- `WC_Stripe_Webhook_Handler::process_checkout_session()` is deprecated since 10.6; success/failure handlers replaced it.
- `wc_stripe_is_checkout_sessions_available` was removed in 10.9; use `wc_stripe_is_adaptive_pricing_supported` only for final cart-specific opt-out.
- Payment Request Button naming/classes are legacy; use Express Checkout terminology and current helpers.
- Gateway/webhook classes are plugin internals. Prefer documented actions and Woo order lifecycle hooks over subclassing or direct handler calls.
## Test matrix
1. Valid, missing, stale, and wrong webhook signatures; matching, mismatched, absent, and unknown account context.
2. Duplicate event delivery and duplicate checkout/3DS return.
3. PaymentIntent success before and after order meta persistence.
4. Checkout Session success, async success/failure, expiration, and amount/currency mismatch across pre-Basil and Basil schemas.
5. Lock collision followed by deferred retry, with the observer firing only after actual processing.
6. Redirect/SCA, capture, refund, dispute, and failed payment.
7. Adaptive Pricing enabled with healthy/disabled webhooks.
8. Invalid webhook-state values and pending-count updates only after a valid signature.
9. HPOS and Action Scheduler worker/cron delays.
## Cross-references
- Use `wc-stripe-future-payments` for charge-and-save and later off-session PaymentIntent state machines.
- Use `wc-payment-gateway` for provider-neutral payment state and webhook design.
- Use `wc-stripe-add-payment-method` for SetupIntent token creation in My Account.
- Use `wc-stripe-subscriptions` for Stripe renewal and subscription change-payment behavior.
- Use `wc-logging` and `wc-action-scheduler-jobs` for general diagnostics.
## References
- Verified source paths:
- `wp-content/plugins/woocommerce-gateway-stripe/includes/class-wc-stripe-webhook-handler.php`
- `wp-content/plugins/woocommerce-gateway-stripe/includes/class-wc-stripe-webhook-state.php`
- `wp-content/plugins/woocommerce-gateway-stripe/includes/class-wc-stripe-order-handler.php`
- `wp-content/plugins/woocommerce-gateway-stripe/includes/class-wc-stripe-order-helper.php`
- `wp-content/plugins/woocommerce-gateway-stripe/includes/class-wc-stripe-api.php`
- `wp-content/plugins/woocommerce-gateway-stripe/includes/class-wc-stripe-helper.php`
- `wp-content/plugins/woocommerce-gateway-stripe/includes/class-wc-stripe-logger.php`
- `wp-content/plugins/woocommerce-gateway-stripe/includes/abilities/class-wc-stripe-abilities-registrar.php`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!