Install on Gumroad
Capture Gumroad sales via the Ping URL webhook relayed to Rekomi's S2S endpoint. Gumroad is the only no-code platform that ships server-side by default.
Gumroad is the one no-code platform Rekomi tracks entirely server-side, not via the browser pixel. The reason: Gumroad does not expose a thank-you-page custom-JS slot for non-admin sellers. The browser-pixel path is closed. Gumroad does, however, expose a Ping URL that fires on every sale, which is what Rekomi's S2S endpoint plugs into.
How attribution works
The affiliate appends ?via=THEIR_SLUG to a Gumroad product URL (or any of the 16 attribution params Rekomi recognizes). When the buyer purchases, Gumroad POSTs a form-encoded payload to your Ping URL handler. The payload includes url_params[via] (Gumroad preserves the original URL query as nested form keys). Your handler reads the slug, then relays the sale to Rekomi's S2S endpoint with an HMAC signature. The match closes the loop.
This is server-side end to end. No cookie, no pixel, no thank-you-page JS. If your affiliate's link makes it to a Gumroad purchase with the via parameter intact, the attribution works.
Configure Gumroad's Ping URL
Step 1. In Gumroad, open Settings > Advanced > Ping URL. Paste the URL of a handler on your backend (e.g., https://yourdomain.com/webhooks/gumroad).
Step 2. Save. Gumroad will POST every sale to this URL going forward.
Gumroad does NOT sign their pings. You should protect your endpoint with one of:
- A shared secret in the URL path or query (
https://yourdomain.com/webhooks/gumroad?secret=xxx) - IP-allowlisting Gumroad's outbound IPs at your edge (Cloudflare, etc.)
- Or both
The HMAC in the next step is between YOUR relay and Rekomi, not validating Gumroad's authenticity, so the secret/IP-allowlist matters for the inbound leg.
Build the relay handler
// Node example with express
const express = require('express');
const crypto = require('crypto');
app.post('/webhooks/gumroad', express.urlencoded({ extended: true }), async (req, res) => {
// 1. Verify shared secret (or IP allowlist at the edge).
if (req.query.secret !== process.env.GUMROAD_SHARED_SECRET) {
return res.sendStatus(401);
}
const sale = req.body;
// 2. Pull the affiliate slug from url_params[via]. Gumroad nests
// the original URL query under url_params; we use extended: true
// above so urlencoded parses the nested keys.
const referral =
sale.url_params?.via ||
sale.url_params?.ref ||
sale.url_params?.fpr;
if (!referral) return res.sendStatus(200); // No affiliate involved.
// 3. Relay to Rekomi. Gumroad already sends price in cents (integer),
// so we do NOT multiply by 100 here.
const body = JSON.stringify({
external_event_id: sale.sale_id,
affiliate_slug: referral,
amount_cents: parseInt(sale.price, 10),
currency: sale.currency,
customer_email: sale.email,
});
const signature = crypto
.createHmac('sha256', process.env.REKOMI_S2S_SECRET)
.update(body)
.digest('hex');
await fetch('https://api.rekomi.com/api/tracking/s2s', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Rekomi-Signature': signature,
},
body,
});
res.sendStatus(200);
});The sale.sale_id becomes external_event_id so retries are idempotent. Rekomi de-dupes on this field; the second call for the same id is a no-op.
Test the install
Append ?via=test to any Gumroad product link and complete a purchase. Within a minute, the conversion should appear on the Rekomi campaign's Activity tab with affiliate_slug: test (or whichever slug you used). If you do not see it, check the relay logs for the response from Rekomi's S2S endpoint.
Refunds
When Gumroad refunds a sale, the Ping URL fires again with refunded: true in the payload. Add a refund branch to your handler:
if (sale.refunded === 'true' || sale.refunded === true) {
const body = JSON.stringify({
external_event_id: sale.sale_id, // Same id as the original sale
refund_reason: 'platform_refund',
});
const signature = crypto.createHmac('sha256', process.env.REKOMI_S2S_SECRET)
.update(body).digest('hex');
await fetch('https://api.rekomi.com/api/tracking/refund', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Rekomi-Signature': signature,
},
body,
});
return res.sendStatus(200);
}Rekomi reverses the commission and updates the affiliate's pending balance.
Quirks worth knowing
Gumroad sends price in cents already (integer). Unlike Stripe's decimal totals or Lemon Squeezy's redirect-URL substitutions, Gumroad's price field is an integer in the lowest currency unit. Multiplying by 100 here gives you a 100x commission, which is the single most common bug on this install.
Form-encoded body with nested keys. Gumroad POSTs application/x-www-form-urlencoded, not JSON. Use a parser that supports nested keys (express.urlencoded({ extended: true }) in Express, qs.parse directly, or equivalents in your stack) so url_params[via] resolves correctly.
No retries on 5xx. Gumroad does NOT retry pings on failure. If your handler returns a non-2xx response, the sale is permanently lost from Rekomi's perspective. Make the relay idempotent (use external_event_id) and run it on infrastructure with strong uptime, or queue the relay through a job runner that retries on its own.
The Ping URL fires on EVERY sale, including ones with no affiliate slug. Your handler should return res.sendStatus(200) early when no affiliate is involved so Gumroad considers the ping handled. Don't 4xx or 5xx in this case; Gumroad will keep pinging.
Affiliate links must include the via parameter explicitly. Unlike Stripe and Paddle (where the cookie carries through automatically), Gumroad has no cookie layer Rekomi controls. The slug has to be in the URL the affiliate shares. Rekomi's affiliate dashboard generates these correctly by default.
Troubleshooting
Ping URL receives the call but Rekomi does not register the conversion. Inspect the body Gumroad sent. If url_params is empty, the affiliate did not include ?via=SLUG in their link. If url_params[via] is present but Rekomi shows no conversion, check the HMAC: it must be computed over the raw JSON.stringify(body) you POST, not the parsed form body Gumroad sent.
Amount is 100x off. You multiplied Gumroad's already-cents price field by 100. Drop the multiplication.
The handler 4xxs Gumroad's pings. Make sure you return 2xx for every payload, even ones with no affiliate slug. If you 4xx, Gumroad treats it as your endpoint rejecting the data but does not retry, so the sale is gone for both reporting and Rekomi attribution.
Related
Install on Lemon Squeezy
Two attribution paths for Lemon Squeezy checkouts. The redirect-URL trick fires a convert event on a brand-owned thank-you page; the order_created webhook relay handles attribution server-side.
Install on Memberstack
Capture Memberstack signups via a post-signup convert event on your host platform. Memberstack is an overlay, so Rekomi installs on the host (Webflow, custom HTML, Duda), not in Memberstack itself.