Integrate
WordPress & WooCommerce
The Flow Affiliate plugin keeps the click a publisher’s link brings to your shop and reports sign-ups, orders and refunds to Flow Affiliate, signed and server to server. No code to write.
The plugin is the website side of events, done for you. It keeps the click from a publisher’s link on your own domain, and when an order is paid it sends the events that attribute the customer and pay the publisher, from your server, signed with your key.
It runs on WordPress 6.0 or later with PHP 7.4 or later. With WooCommerce 8 or later it reports orders, refunds and cancellations, with WooCommerce’s order tables (HPOS) and with both the classic and the block checkout.
Install
Download flow-affiliate-wordpress.zip. In WordPress, go to Plugins → Add New Plugin → Upload Plugin, choose the zip and activate it.
In Flow Affiliate, open your program’s setup, step 2 Send events server to server, and create a test key. You get a key id that starts with
fg_test_and a secret. The secret is shown once: copy it before you close the dialog.In WordPress, go to Settings → Flow Affiliate. Paste the key id and the secret, keep the environment on Test and save. Then choose Test connection: it sends one signed request with the saved key, and nothing is recorded in Flow Affiliate.
The secret is never shown again on the page; leave the field empty to keep it. A test key under Production, or the other way round, is refused.
Once Flow Group has approved your program, create a production key in the same step, paste it, switch the environment to Production and test the connection again.
How attribution works
- 1Visitor → Flow AffiliateOpens a publisher’s link,
https://go.affiliate.yflow.io/a/<code> - Flow Affiliate → VisitorRecords the click and redirects to your landing page with
fg_clickandfg_ref - 2Visitor → Your shopLands: the plugin keeps the click in a cookie on your domain
- 3Visitor → Your shopChecks out: the click is copied onto the order
- 4Your shop → Flow AffiliateThe order is paid:
user.registeredandpurchase.completed
- Flow Affiliate redirects the visitor to your landing page with
?fg_click=<click id>&fg_ref=<code>, plus the link’s UTM parameters. - On that first page view the plugin keeps both in a first-party cookie,
flow_affiliate: HttpOnly, SameSite=Lax, and Secure on an https site. It lasts 30 days unless you change Cookie lifetime. A later link replaces it, so the last click counts. A value that is not a click id, or a code that is not 3 to 24 letters and digits, is ignored. - At checkout the click is copied onto the order.
- When the order is paid,
user.registeredcarries the click id and the code. It is the one event that ties the customer to the publisher; Flow Affiliate checks it against your program’s attribution window and rules. - Every later order from the same customer account sends
purchase.completed, link or not. What it pays is up to your offer.
A customer account is tied to the first publisher whose link led to a paid order. A guest has no account, so each guest order is a customer of its own.
What is sent, and when
| When | Events | appUserId |
|---|---|---|
| An order with a click or a publisher coupon is paid (Processing or Completed) | user.registered, then purchase.completed, in one request | wc:<customer id>, or wc-guest:<order id> for a guest |
| A paid order from a customer Flow Affiliate already knows | purchase.completed | wc:<customer id> |
| A refund, full or partial | purchase.refunded | the order’s |
| A paid order is cancelled | purchase.refunded | the order’s |
| Someone who came through a link creates an account, with Sign-ups on | user.registered | wc:<user id>, or wp:<user id> without WooCommerce |
Any other order is not sent: Flow Affiliate never needs data about customers no publisher brought. Each order is sent once, however often its status changes after it is paid.
The event ids are registered:<appUserId>, order:<order id>, refund:<refund id> and cancelled:<order id>. Flow Affiliate deduplicates on them, so a retry never counts twice.
{
"eventId": "order:1042",
"type": "purchase.completed",
"occurredAt": "2026-11-12T10:21:00Z",
"appUserId": "wc:15",
"amount": 4500,
"currency": "USD",
"metadata": {
"source": "woocommerce",
"orderNumber": "1042",
"gross": 5785,
"tax": 485,
"shipping": 800,
"discount": 500,
"items": [{ "productId": 7, "sku": "TEE-M", "name": "T-shirt", "quantity": 2, "amount": 3600 }],
"coupons": ["summer10"],
"isFirst": true
}
}The amount
amount is what commission is paid on: the order’s line items after discounts, without tax, shipping or fees, in minor units (cents) of the order’s currency. Everything else is in metadata:
grossintegertax, shipping, discountintegeritemsarrayproductId, sku, name, quantity and amount after discounts, without tax.couponsstring[]isFirstbooleanorderNumberstringuser.registered carries the customer’s billing country.
Refunds and cancellations
Each refund sends purchase.refunded with reversesEventId set to the order’s order:<order id>. Its amount is the commissionable part of that refund: the refunded lines after discounts, or, when only an amount was refunded, its share of the order. The refund that completes the order takes back whatever the earlier ones left. metadata.partial says whether money is still left on the order, and metadata.gross is the refund as entered.
A paid order that is cancelled is reversed the same way, with metadata.cancelled.
What was already settled or paid is never clawed back automatically: it is flagged for review instead. See reversals.
Coupons
A coupon’s code is never sent as a publisher’s code on its own. Publishers choose their own codes, so a generic coupon such as SUMMER10 could match one, and every order using it would be paid to that publisher.
For a coupon you gave a publisher, open it under Marketing → Coupons and fill in Flow publisher code on the General tab with their code. An order using that coupon sends the code as affiliateCode on user.registered, with or without a link. When the visitor also came through a link, both the click and the code are sent, and your program’s attribution model decides between them.
Testing with a test key
- Keep the environment on Test, with the test key.
- Open one of your program’s links in a private window. It lands on your shop with
fg_clickin the address. - Place an order with a payment method that needs no money, such as cash on delivery, and mark it Processing.
- Settings → Flow Affiliate → Recent deliveries shows the order as Sent, with
2 accepted. In Flow Affiliate, step 2 of your program’s setup shows the sign-up.
Test events are processed like production ones, but nothing is paid for them, and a test order never takes a real click away from a production customer. Refund the order to see purchase.refunded go out too.
Delivery and retries
Each batch of events is kept in its own table and sent by Action Scheduler (WooCommerce → Status → Scheduled Actions, group flow-affiliate), or by WP-Cron without WooCommerce. Every attempt is signed anew.
A network error, 429, a 5xx or stale_timestamp is tried again after 1 and 5 minutes, then 15 minutes, 1, 3, 6, 12 and 24 hours. Anything else, such as invalid_key or invalid_signature, needs a fix: correct the settings, then choose Retry on the delivery in Recent deliveries. The log keeps the last 500 deliveries.
Without WooCommerce
Turn on Sign-ups and the plugin sends user.registered when someone who came through a link creates an account.
Anything else your site does, send with the flow_affiliate_track action. It is queued, signed and retried like the shop’s events:
do_action( 'flow_affiliate_track', 'subscription.started', 'wp:' . $user_id, array(
'eventId' => 'subscription:' . $subscription_id,
'amount' => 999, // cents
'currency' => 'USD',
) );Arguments
$typestringrequiredsubscription.started, or one of your own events.$app_user_idstringrequiredwc:<user id> with WooCommerce, wp:<user id> without.eventIdstringoccurredAtint | string | DateTimeInterfaceamount, currencyint, stringreversesEventIdstringeventId this event undoes.metadataarrayclickId, affiliateCodestringuser.registered without them takes the visitor’s cookie.Filters
flow_affiliate_set_cookie($set, $click)false to not keep the click, for example until your consent tool allows it.flow_affiliate_order_events($events, $order)flow_affiliate_send_refund($send, $refund, $order, $partial)false to not report a refund.Privacy
Customers are named only by pseudonymous ids: wc:<customer id>, or wc-guest:<order id> for a guest. Names, email addresses, phone numbers and street addresses are never sent. Events carry the billing country, the products and the coupon codes. Deleting the plugin removes its settings, its delivery log and everything it stored on orders, coupons and users.