boxcart.dev

REST API

BoxCart exposes a WordPress REST API endpoint for Stripe webhook processing, providing real-time payment status updates for your orders.

Overview

BoxCart currently exposes one REST API endpoint dedicated to Stripe webhook processing. The endpoint is registered by the BoxCart_Webhook class, which hooks into the rest_api_init action during WordPress initialisation.

The REST API is used as a callback URL for Stripe to notify your site about payment events in real time. This serves as a reliable backup to the synchronous PaymentIntent status check that runs when a customer is redirected back to your site after payment.

Registration (class-boxcart-webhook.php)
add_action( 'rest_api_init', array( $this, 'register_routes' ) );

register_rest_route(
    'boxcart/v1',
    '/webhook/stripe',
    array(
        'methods'             => 'POST',
        'callback'            => array( $this, 'handle_stripe_webhook' ),
        'permission_callback' => '__return_true',
    )
);

Stripe Webhook Endpoint

PropertyValue
MethodPOST
URL/wp-json/boxcart/v1/webhook/stripe
HandlerBoxCart_Webhook::handle_stripe_webhook()
ProcessingBoxCart_Stripe::handle_webhook_event()
AuthStripe signature verification (no WordPress auth required)

The full URL for your site will be:

Webhook URL
https://yoursite.com/wp-json/boxcart/v1/webhook/stripe

This URL is also displayed as a read-only field in BoxCart → Settings → Payments when Stripe is enabled, so you can copy it directly from there.

Authentication

The webhook endpoint does not use WordPress authentication. Instead, it relies on Stripe signature verification using HMAC-SHA256 to ensure that incoming requests genuinely originate from Stripe.

When Stripe sends a webhook event, it includes a Stripe-Signature header containing a timestamp and one or more signatures. BoxCart verifies these by:

  1. Parsing the Stripe-Signature header to extract the timestamp (t) and signature(s) (v1).
  2. Checking that the timestamp is within a 5-minute tolerance window to prevent replay attacks.
  3. Constructing the signed payload as {timestamp}.{raw_body}.
  4. Computing the expected signature using hash_hmac('sha256', $signed_payload, $webhook_secret).
  5. Comparing the expected signature against the provided signature(s) using hash_equals() for timing-safe comparison.
Keep your webhook secret safe

The webhook secret is a sensitive credential. Never expose it in client-side code, commit it to version control, or share it publicly. It is stored as a password-type field in BoxCart settings and should be treated with the same care as your Stripe secret key.

Supported Events

The webhook handler processes two Stripe event types. All other event types are acknowledged with a 200 response but no action is taken.

Stripe EventActionDetails
payment_intent.succeeded Marks order as paid Updates payment_status to paid and stores the PaymentIntent ID. If the order status is still pending, it advances to processing.
payment_intent.payment_failed Marks payment as failed Updates payment_status to failed. The order is not deleted — it remains in the database for reference.

Both events use the order_id stored in the PaymentIntent's metadata to look up the corresponding BoxCart order. If the order_id is missing from the metadata or the order cannot be found, the event is logged and skipped.

Event handling (class-boxcart-stripe.php)
public function handle_webhook_event( $event ) {
    $type = isset( $event['type'] ) ? $event['type'] : '';
    $data = isset( $event['data']['object'] ) ? $event['data']['object'] : array();

    switch ( $type ) {
        case 'payment_intent.succeeded':
            return $this->handle_payment_succeeded( $data );

        case 'payment_intent.payment_failed':
            return $this->handle_payment_failed( $data );

        default:
            // Unhandled event type — acknowledge receipt.
            return true;
    }
}

Setting Up Webhooks

Follow these steps to configure Stripe webhooks for your BoxCart store.

  1. Open the Stripe Dashboard

    Log in to your Stripe Dashboard and navigate to Developers → Webhooks.

  2. Add a new endpoint

    Click Add endpoint. In the Endpoint URL field, enter your BoxCart webhook URL:

    https://yoursite.com/wp-json/boxcart/v1/webhook/stripe

    Replace yoursite.com with your actual domain.

  3. Select events to listen for

    Click Select events and choose the following two events:

    • payment_intent.succeeded
    • payment_intent.payment_failed

    Click Add events, then Add endpoint to save.

  4. Copy the signing secret

    After creating the endpoint, Stripe will display a Signing secret that begins with whsec_. Click to reveal and copy this value.

  5. Paste the secret into BoxCart

    In your WordPress admin, go to BoxCart → Settings → Payments. Paste the signing secret into the Webhook Secret field and save your settings.

Test mode and live mode use separate secrets

If you are using Stripe in test mode, make sure you create the webhook endpoint in the Stripe test mode dashboard and use the corresponding test signing secret. When you switch to live mode, you will need to create a separate live webhook endpoint and update the secret in BoxCart accordingly.

Request Format

Stripe sends webhook events as POST requests with a JSON body. The request includes a Stripe-Signature header used for verification.

Example request headers
POST /wp-json/boxcart/v1/webhook/stripe HTTP/1.1
Host: yoursite.com
Content-Type: application/json
Stripe-Signature: t=1681000000,v1=abc123def456...
Example JSON body (payment_intent.succeeded)
{
  "id": "evt_1abc123",
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_1xyz789",
      "status": "succeeded",
      "amount": 2500,
      "currency": "gbp",
      "metadata": {
        "order_id": "42"
      }
    }
  }
}

BoxCart reads the raw request body via $request->get_body() and retrieves the signature header via $request->get_header('stripe-signature'). The JSON payload is decoded only after signature verification passes.

Response Codes

The webhook endpoint returns the following HTTP status codes.

Status CodeMeaningWhen Returned
200 OK Event received and processed The signature is valid and the event has been handled (or acknowledged for unhandled event types). Response body: {"received": true}
400 Bad Request Invalid payload or signature The Stripe-Signature header is missing, the signature verification failed, or the JSON payload could not be parsed. Response body: {"error": "..."}

Stripe considers any 2xx response as successful delivery. If your endpoint returns a non-2xx response, Stripe will retry the delivery with exponential backoff for up to 3 days.

Tip

BoxCart always returns 200 for successfully verified events, even if the specific event type is not handled. This prevents unnecessary retries from Stripe for event types that BoxCart does not process.

Testing Webhooks

There are two primary ways to test webhook delivery during development.

Using the Stripe CLI

The Stripe CLI lets you forward webhook events to your local development environment without needing a publicly accessible URL.

Forward events to your local site
stripe listen --forward-to yoursite.com/wp-json/boxcart/v1/webhook/stripe

The CLI will output a webhook signing secret (starting with whsec_). Copy this temporary secret and paste it into your BoxCart webhook secret setting for local testing.

You can then trigger test events from another terminal:

Trigger a test event
stripe trigger payment_intent.succeeded

Using the Stripe Dashboard

If your site is publicly accessible (e.g. a staging server), you can send test events directly from the Stripe Dashboard:

  1. Go to Developers → Webhooks in the Stripe Dashboard.
  2. Click on your webhook endpoint.
  3. Click Send test webhook.
  4. Select an event type (e.g. payment_intent.succeeded) and click Send test webhook.

Check the endpoint's Recent deliveries section to see the request and response details, including status codes and any error messages.

Tip

Test webhook events from Stripe will not contain a real order_id in the metadata. The event will be acknowledged with a 200 response, but no order will be updated. To test the full flow end-to-end, place a test order using Stripe test card numbers.