Guide · WooCommerce

Track WooCommerce Sales Without an Extension

Your store knows what it sold. It doesn't know which newsletter, story, or post sold it. The hook already exists in WooCommerce: all it needs is the call.

Two routes — and the one that doesn't work

The native webhook

  • Settings → Advanced → Webhooks: no code needed
  • No custom header possible
  • Signs with X-WC-Webhook-Signature and nothing else
  • Cannot therefore reach the Subtraq API
  • Only useful for notifying middleware

Twelve lines of PHP

  • The woocommerce_payment_complete hook
  • wp_remote_post sets the Authorization header
  • No extension, no extra subscription
  • Order number serves as the idempotency key
  • And the click ID can travel with it

WooCommerce's native webhook accepts no custom header: it cannot carry the server key Subtraq requires. That's not a Subtraq limitation, it's a WooCommerce boundary — and working around it takes one HTTP call, not an extension.

The seven steps, one by one

If you don't want to touch PHP

The no-code route exists, and it's honest

It costs a subscription and one more place things can break. In return, nobody touches the site, and a WordPress update can't erase anything.

One detail that matters: WooCommerce has no order.paid topic. Its native topics are order creation and order update. So the middleware must recognize that an order has just moved to "completed" or "processing", depending on how you work.

Source: the WooCommerce webhooks documentation, recorded September 12, 2026.

The Make guide, step by step

Two links instead of one

The native webhook notifies the middleware

WooCommerce sends order.updated to Zapier, Make, or n8n, and the middleware calls Subtraq with the authorization header. Filter on the order status: without a filter, every status change would fire.

The PHP route, 7 steps

From the payment hook to the attributed sale

No extension to install, no setting to find: the hook has been part of WooCommerce forever, and the HTTP call is part of WordPress.

1

In Subtraq, create a server key

Settings → API Keys. It starts with stq_sk_, carries the events:write permission, and is shown only once. Also note the client workspace slug.

2

Store the key outside the code

A constant in wp-config.php, or your host's secret manager. Never in a theme file: themes get shared, copied, and sometimes end up in a public repository.

3

Hook into woocommerce_payment_complete

This is the hook fired when a payment succeeds, called by the order's payment_complete method. It's the only one that truly says 'there's a number to record' — order creation doesn't say that.

4

Build the body and send it

wp_remote_post makes the call, with the Authorization header set to Bearer followed by the key. Five fields: space, amount in cents, currency, invoiceId, email. The order total comes from get_total, the email from get_billing_email.

5

Multiply by one hundred, and round explicitly

Subtraq counts in cents and rejects decimal numbers. A total of $43.90 must leave as 4390. Use an explicit round: PHP floats know how to turn 43.90 into 4389 if you let them.

6

Put the order number in invoiceId

get_order_number gives a stable value. An order whose status goes back and forth sends the same ID: Subtraq recognizes the duplicate, responds 200, and adds nothing.

7

Put the code in the right place

In a small site-specific plugin (mu-plugin) or in a child theme's functions.php. Never in the parent theme's functions.php: the next theme update would erase it silently.

Hook names and method names recorded in the WooCommerce documentation on September 12, 2026 — the payment gateway interface and the metadata methods. Interfaces change: if what you see no longer matches, the source is authoritative.

The tricky part

Linking the order to the click, not just the email

The sequence is: when the visitor arrives, store st_id somewhere that survives their navigation; when the order is created, write it as metadata; at payment, send it in the clickId field. A few more lines, and attribution becomes exact rather than probable.

Without that, the path is still the email address: Subtraq looks up the person within the workspace attribution window, thirty days by default. That works well, given two conditions — that the email is the same on both sides, and that the click is still within the window.

Both can travel together: the route accepts email, externalId, and clickId in the same request, and any one of the three is enough.

What attribution means

What WooCommerce can hold

A metadata field on the order

A WooCommerce order object reads and writes metadata via get_meta, update_meta_data, and save. A Subtraq link passes a parameter named st_id to its destination, in the form clk_ followed by twenty-four characters.

Tricky cases, handled honestly

A WordPress store lives among extensions and updates. Here is what breaks, and what doesn't.

What about an Elementor funnel in front of the store?

It's a different action, and simpler: an Elementor form has a Redirect action, and you just paste a Subtraq link there to turn every submission into an attributed lead — no key, no PHP, no request. The WordPress and Elementor guide shows the exact path. Keep the payment hook for sales, and the redirect for leads.

An order gets refunded.

WooCommerce does fire woocommerce_order_refunded, but Subtraq's route only accepts positive or zero amounts, and there's no call that cancels a recorded sale. The refund doesn't subtract itself: keep a trace of it with a zero-amount event, then correct the report manually. Better to say that clearly than to have a wrong total appear in front of the client.

The order goes through several statuses. How many sales?

One, because the hook chosen is the payment one, not the status-change one. And if your setup fires it twice, the order number in invoiceId means the second pass responds 200 without adding anything.

An offline payment — bank transfer or check?

The payment hook doesn't fire until the order is marked paid. When you manually move it to "completed", that's the moment to hook — and on that day, the attribution window becomes the real question: extend it if your payments take weeks.

Frequently asked questions

Is really no extension needed?

None. The payment hook and the HTTP call are part of WooCommerce and WordPress. And if you don't want to touch PHP, the middleware route installs nothing on the site either.

Where should the code go to survive updates?

In a small site-specific plugin, or in a child theme's functions.php. A parent theme's functions.php is overwritten on every theme update, without warning.

Is a pixel required on the store?

No. Nothing is added to page rendering for sales: it's server talking to server. The page pixel is only for leads and events, and it never accepts an amount.

Can the server key leak?

It must never reach a browser. Stored in a configuration constant, it stays server-side. That's also why amounts never go through the browser: a number written from a page is worthless.

What if Subtraq is unavailable when the order comes in?

The order exists on your end regardless. Log the failed call and resend it later with the same order number: the invoice ID guarantees it won't count twice.

Do your payments go through Stripe?

Then don't go through WooCommerce: the Stripe integration records every payment on its own, with mandatory signature and nothing to code.

The Stripe guide

Your next paid order can say where it came from

Create an account, grab a server key, hook up the payment hook. Twelve lines, and the store answers the question.

Create a free account