Contents
On this page
Stripe Webhook - Plugixa Chauffeur
POST /plugixa-chauffeur/v1/public/stripe-webhook
The endpoint Stripe calls, server to server, to report payments, refunds and disputes. You register it in Stripe; you do not call it yourself.
Public: no login and no capability. It is authenticated by Stripe’s signature instead of a login.
Note. The endpoint always answers
200, even to a request it rejects: Stripe would otherwise retry for days, and a retried bad signature is still bad. Readreceivedto see whether the event was accepted.
Request
POST https://your-site.example/wp-json/plugixa-chauffeur/v1/public/stripe-webhook
Request body
Content type: application/json
The body is the Stripe event, sent by Stripe as is. Shown here shortened.
{
"id": "evt_1QaBcD2eZvKYlo2C",
"type": "checkout.session.completed",
"data": {
"object": {
"id": "cs_live_a1B2c3D4e5F6",
"payment_status": "paid",
"payment_intent": "pi_3QaBcD2eZvKYlo2C1x9yAbCd",
"client_reference_id": "42",
"metadata": {
"booking_id": "42",
"booking_number": "PXC-20261112-0815"
}
}
}
}
Setting it up
Register this URL in your Stripe Dashboard (Developers -> Webhooks), subscribe it to the events below, and paste its signing secret (whsec_...) into Webhook signing secret in Settings.
https://your-site.example/wp-json/plugixa-chauffeur/v1/public/stripe-webhook
The signature check
Before anything in the body is read, the Stripe-Signature header is checked against the raw body and the signing secret: it must hold a timestamp t and at least one v1 signature, the timestamp must be within 300 seconds of the server clock, and the HMAC-SHA256 of t.body, keyed with the secret, must equal one of the v1 signatures. With no signing secret saved, every call is rejected.
Events
| Event | What it changes |
|---|---|
checkout.session.completed |
A paid session marks the booking paid and moves it from pending to processing. An unpaid one (a bank debit still processing) sets the payment status to pending |
checkout.session.async_payment_succeeded |
The delayed payment arrived: paid, as above |
checkout.session.async_payment_failed |
Payment status failed |
checkout.session.expired |
Nothing changes; the plugixa_chauffeur_stripe_notice action fires |
refund.created, refund.updated |
partially_refunded or refunded; a full refund also sets an open booking to refunded |
refund.failed |
Nothing changes; the notice action fires |
charge.dispute.created |
Payment status disputed |
charge.dispute.closed |
Back to paid when the dispute was won or closed as an inquiry, else stays disputed |
The booking is found from the booking_id the plugin puts in the session and payment metadata, so refunds and disputes made in the Stripe Dashboard are matched too. A paid booking is never moved back to pending or failed by a late or replayed event, and the same event twice changes nothing.
Responses
200 Always. received says whether the event was acted on.
This response is not wrapped in the success envelope.
{
"received": true
}
| Field | Type | Meaning |
|---|---|---|
received |
boolean |
true when the signature is valid and the event is one listed above; false otherwise, including for an event type the plugin ignores |
Errors
This endpoint never answers with an error status.
Plus the errors every endpoint can return.
Examples
curl -s -X POST "https://your-site.example/wp-json/plugixa-chauffeur/v1/public/stripe-webhook" \
-H "Stripe-Signature: $STRIPE_SIGNATURE" \
-H "Content-Type: application/json" \
-d '{"id":"evt_1QaBcD2eZvKYlo2C","type":"checkout.session.completed","data":{"object":{"id":"cs_live_a1B2c3D4e5F6","payment_status":"paid","payment_intent":"pi_3QaBcD2eZvKYlo2C1x9yAbCd","client_reference_id":"42","metadata":{"booking_id":"42","booking_number":"PXC-20261112-0815"}}}}'
<?php
$ch = curl_init('https://your-site.example/wp-json/plugixa-chauffeur/v1/public/stripe-webhook');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Stripe-Signature: ' . getenv('STRIPE_SIGNATURE'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'id' => 'evt_1QaBcD2eZvKYlo2C',
'type' => 'checkout.session.completed',
'data' => [
'object' => [
'id' => 'cs_live_a1B2c3D4e5F6',
'payment_status' => 'paid',
'payment_intent' => 'pi_3QaBcD2eZvKYlo2C1x9yAbCd',
'client_reference_id' => '42',
'metadata' => [
'booking_id' => '42',
'booking_number' => 'PXC-20261112-0815',
],
],
],
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://your-site.example/wp-json/plugixa-chauffeur/v1/public/stripe-webhook', {
method: 'POST',
headers: {
'Stripe-Signature': process.env.STRIPE_SIGNATURE,
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: 'evt_1QaBcD2eZvKYlo2C',
type: 'checkout.session.completed',
data: {
object: {
id: 'cs_live_a1B2c3D4e5F6',
payment_status: 'paid',
payment_intent: 'pi_3QaBcD2eZvKYlo2C1x9yAbCd',
client_reference_id: '42',
metadata: { booking_id: '42', booking_number: 'PXC-20261112-0815' },
},
},
}),
});
const { received } = await response.json();
Quick Links
Share