Skip to content
IntegrateWordPress & WooCommerce

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

  1. Download flow-affiliate-wordpress.zip. In WordPress, go to Plugins → Add New Plugin → Upload Plugin, choose the zip and activate it.

  2. 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.

  3. 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.

  4. 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

  1. 1Visitor → Flow Affiliate
    Opens a publisher’s link, https://go.affiliate.yflow.io/a/<code>
  2. Flow Affiliate → Visitor
    Records the click and redirects to your landing page with fg_click and fg_ref
  3. 2Visitor → Your shop
    Lands: the plugin keeps the click in a cookie on your domain
  4. 3Visitor → Your shop
    Checks out: the click is copied onto the order
  5. 4Your shop → Flow Affiliate
    The order is paid: user.registered and purchase.completed
  1. Flow Affiliate redirects the visitor to your landing page with ?fg_click=<click id>&fg_ref=<code>, plus the link’s UTM parameters.
  2. 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.
  3. At checkout the click is copied onto the order.
  4. When the order is paid, user.registered carries 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.
  5. 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

WhenEventsappUserId
An order with a click or a publisher coupon is paid (Processing or Completed)user.registered, then purchase.completed, in one requestwc:<customer id>, or wc-guest:<order id> for a guest
A paid order from a customer Flow Affiliate already knowspurchase.completedwc:<customer id>
A refund, full or partialpurchase.refundedthe order’s
A paid order is cancelledpurchase.refundedthe order’s
Someone who came through a link creates an account, with Sign-ups onuser.registeredwc:<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:

grossinteger
The order total as the customer paid it, tax and shipping included.
tax, shipping, discountinteger
The order’s tax, shipping (without tax) and discount.
itemsarray
The first 50 lines: productId, sku, name, quantity and amount after discounts, without tax.
couponsstring[]
The coupon codes on the order, as they are.
isFirstboolean
For a customer account: whether the customer had no earlier paid order. A payout can match on it to pay for first orders only.
orderNumberstring
The number WooCommerce shows for the order.

user.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

  1. Keep the environment on Test, with the test key.
  2. Open one of your program’s links in a private window. It lands on your shop with fg_click in the address.
  3. Place an order with a payment method that needs no money, such as cash on delivery, and mark it Processing.
  4. 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

$typestringrequired
The event type, such as subscription.started, or one of your own events.
$app_user_idstringrequired
Your pseudonymous id for the user. Use the same one as the plugin: wc:<user id> with WooCommerce, wp:<user id> without.
eventIdstring
Keep it stable for the occurrence: Flow Affiliate deduplicates on it. Generated when left out, which is only safe for events you never send twice.
occurredAtint | string | DateTimeInterface
Defaults to now.
amount, currencyint, string
Minor units with an ISO 4217 code.
reversesEventIdstring
The eventId this event undoes.
metadataarray
Keys and values, sent as an object.
clickId, affiliateCodestring
A user.registered without them takes the visitor’s cookie.

Filters

flow_affiliate_set_cookie($set, $click)
Return false to not keep the click, for example until your consent tool allows it.
flow_affiliate_order_events($events, $order)
Change a paid order’s events before they are queued.
flow_affiliate_send_refund($send, $refund, $order, $partial)
Return 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.