| 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 validation, order resolution, PaymentIntent and Checkout Session deferral, Action Scheduler, order locks, idempotency, safe extension hooks, Adaptive Pricing dependence, unexpected-charge detection, logging, and deprecated Stripe hooks/classes. Use for wc_stripe_webhook_received, payment_intent events, checkout.session events, pending Stripe orders, duplicate settlement, custom webhook observers, or Stripe reconciliation. |
| metadata | {"wp-skills-author":"Soczo Kristof","wp-skills-contact":"mailto:lonsdale201@hotmail.com","wp-skills-plugin":"woocommerce-gateway-stripe","wp-skills-plugin-version-tested":"10.8.4","wp-skills-woocommerce-version-tested":"10.9.4","wp-skills-php-min":"7.4","wp-skills-last-updated":"2026-07-20"} |
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():
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.
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:
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.
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:
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:
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.
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.
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:
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:
wp action-scheduler 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.
- 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
- Valid, missing, stale, and wrong webhook signatures.
- Duplicate event delivery and duplicate checkout return.
- PaymentIntent success before and after order meta persistence.
- Checkout Session success, async success/failure, and expiration.
- Lock collision followed by deferred retry.
- Redirect/SCA, capture, refund, dispute, and failed payment.
- Adaptive Pricing enabled with healthy/disabled webhooks.
- 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