Skip to main content
This guide shows how to connect a WooCommerce store to Pocketsflow so that customers pay through Pocketsflow’s hosted checkout while their order is tracked in WooCommerce. It uses only public Pocketsflow APIs and webhooks — no plugin marketplace listing required. (A native, no-code WooCommerce app is tracked separately on our roadmap.)

How it works

Pocketsflow acts as a redirect payment gateway for WooCommerce: The buyer’s WooCommerce order id travels in metadata and is echoed back on the order.completed webhook, which is how WooCommerce knows which order to mark as paid.

Prerequisites

  • A Pocketsflow account with at least one product.
  • An API key (pk_live_… for production, pk_test_… while developing). Create one under Developers → API keys.
  • A WooCommerce store you can add a small custom plugin / snippet to.
Develop against a pk_test_… key first. Test-mode checkout sessions don’t move real money, and you’ll receive test-mode webhooks so you can validate the full flow end to end.

Step 1 — Map WooCommerce products to Pocketsflow products

Each WooCommerce product that should be paid through Pocketsflow needs a corresponding Pocketsflow productId. Create the product in the dashboard (or via POST /products) and store the id on the WooCommerce product, e.g. as a custom field _pocketsflow_product_id.
// Save the mapping on the WooCommerce product (admin side)
update_post_meta( $wc_product_id, '_pocketsflow_product_id', '65a1b2c3d4e5f6a7b8c9d0e1' );

Step 2 — Create a checkout session when the order is placed

Register Pocketsflow as a payment gateway and, in process_payment(), create a checkout session and redirect the buyer to the returned url.
class WC_Gateway_Pocketsflow extends WC_Payment_Gateway {

    public function process_payment( $order_id ) {
        $order = wc_get_order( $order_id );

        // For simplicity, assume a single line item mapped to a Pocketsflow product.
        $items = $order->get_items();
        $item  = reset( $items );
        $pf_product_id = get_post_meta( $item->get_product_id(), '_pocketsflow_product_id', true );

        $response = wp_remote_post( 'https://api.pocketsflow.com/checkout/sessions', array(
            'headers' => array(
                'Authorization' => 'Bearer ' . POCKETSFLOW_API_KEY,
                'Content-Type'  => 'application/json',
            ),
            'timeout' => 20,
            'body'    => wp_json_encode( array(
                'productId'     => $pf_product_id,
                'customerEmail' => $order->get_billing_email(),
                'successUrl'    => $this->get_return_url( $order ),
                'cancelUrl'     => wc_get_checkout_url(),
                // Echoed back on the webhook so we can find this order later:
                'metadata'      => array(
                    'woocommerce_order_id' => (string) $order_id,
                ),
            ) ),
        ) );

        if ( is_wp_error( $response ) ) {
            wc_add_notice( 'Payment error: unable to start checkout.', 'error' );
            return array( 'result' => 'failure' );
        }

        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        // Mark the WC order as pending until the webhook confirms payment.
        $order->update_status( 'pending', 'Awaiting Pocketsflow payment.' );

        return array(
            'result'   => 'success',
            'redirect' => $body['url'], // hosted Pocketsflow checkout
        );
    }
}
The successUrl is where the buyer lands after paying — WooCommerce’s standard “order received” page ($this->get_return_url( $order )). The cancelUrl sends them back to the cart if they abandon checkout.

Step 3 — Register a webhook

Tell Pocketsflow where to send events. You can do this once from the dashboard (Developers → Webhooks) or via the API:
curl -X POST https://api.pocketsflow.com/webhooks \
  -H "Authorization: Bearer pk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://store.example.com/?wc-api=pocketsflow_webhook",
    "events": ["order.completed", "order.refunded"]
  }'
The response includes a signing secret (shown once). Store it securely — you need it to verify incoming webhooks.

Step 4 — Handle the webhook and mark the order paid

Pocketsflow signs every webhook so you can confirm it really came from us. Each delivery includes these headers:
HeaderDescription
X-Pocketsflow-EventThe event type, e.g. order.completed.
X-Pocketsflow-SignatureHMAC-SHA256 of the raw request body, keyed with your signing secret (hex).
X-Pocketsflow-TimestampWhen the event was sent (ms since epoch).
Verify the signature against the raw body, then use the metadata.woocommerce_order_id to settle the right order:
add_action( 'woocommerce_api_pocketsflow_webhook', function () {
    $raw       = file_get_contents( 'php://input' );
    $signature = $_SERVER['HTTP_X_POCKETSFLOW_SIGNATURE'] ?? '';
    $event     = $_SERVER['HTTP_X_POCKETSFLOW_EVENT'] ?? '';

    // 1. Verify the signature (timing-safe).
    $expected = hash_hmac( 'sha256', $raw, POCKETSFLOW_WEBHOOK_SECRET );
    if ( ! hash_equals( $expected, $signature ) ) {
        status_header( 400 );
        exit( 'Invalid signature' );
    }

    $payload = json_decode( $raw, true );
    $wc_order_id = $payload['metadata']['woocommerce_order_id'] ?? null;
    $order = $wc_order_id ? wc_get_order( $wc_order_id ) : null;

    if ( ! $order ) {
        status_header( 200 ); // Nothing to do — acknowledge so we don't retry.
        exit( 'OK' );
    }

    // 2. Idempotency — don't double-process.
    if ( $order->is_paid() ) {
        status_header( 200 );
        exit( 'Already processed' );
    }

    // 3. React based on the event type.
    if ( $event === 'order.completed' ) {
        $order->payment_complete( $payload['order']['id'] ?? '' );
        $order->add_order_note( 'Paid via Pocketsflow.' );
    } elseif ( $event === 'order.refunded' ) {
        $order->update_status( 'refunded', 'Refunded in Pocketsflow.' );
    }

    status_header( 200 );
    exit( 'OK' );
} );
Always verify against the raw, unparsed request body. Re-serializing the JSON (e.g. json_encode(json_decode($raw))) can reorder keys and break the signature check.

Step 5 — Test the full flow

  1. Switch your gateway to use a pk_test_… key.
  2. Place a test order in WooCommerce and complete the hosted checkout.
  3. Confirm the buyer is redirected back to the WooCommerce “order received” page.
  4. Confirm the order.completed webhook arrives and the order flips to Processing / Completed.
You can also resend a test event from Developers → Webhooks → ⋯ → Send test to exercise your handler without placing an order.

Handling edge cases

They’re returned to your cancelUrl and the WooCommerce order stays pending. WooCommerce’s “hold stock” / cancel-unpaid-orders settings will clean these up.
Webhooks are the source of truth for payment. If one is missed, reconcile by listing recent orders via GET /orders and matching on metadata.woocommerce_order_id. Keep your handler idempotent so a re-send is safe.
The example assumes one mapped product per order. For carts with several items, create a checkout session per mapped line item, or model the cart as a single Pocketsflow product. Mixed carts are covered as a follow-up in the integration spec.

Reference