Coupon code tracking
Create per-affiliate Stripe promotion codes, attribute conversions on redemption, and clawback refunds automatically.
Rekomi mints Stripe coupons + promotion codes on your behalf and credits the right affiliate when a customer redeems one at Stripe Checkout. No click cookie required, no tracking script. Use this when your buyers will type a code rather than click a link (influencer programs, podcast spots, offline ads).
Plan tier required: Growth or higher for direct rk_live_* API access. JWT (dashboard) calls work on every paid plan.
Concepts
- Campaign coupon: the discount semantics. Maps 1:1 to a Stripe
coupon. You set the percent or amount, duration, max redemptions, expiry. Once created, the discount is immutable (Stripe rule). To change a discount, archive the parent and create a new one. - Affiliate coupon: a redeemable code attached to one affiliate. Maps 1:1 to a Stripe
promotion_code. Multiple per affiliate allowed (vanity variants likeSARAH20,SARAHSUMMER).
Attribution: when a customer redeems a promotion code at checkout, Rekomi's Stripe webhook sees invoice.discounts[].promotion_code and credits the affiliate who owns it. If OverridesClickAttribution is on (default), the coupon wins even when there's a click cookie. Refunds via charge.refunded decrement the redemption count and mark the conversion refunded.
Authentication
All endpoints accept either a dashboard JWT or a public-API bearer:
Authorization: Bearer rk_live_xxxGet a key in /dashboard/settings/api. Reads require read scope; writes require read_write.
Create a campaign coupon
POST /api/v1/campaigns/{programId}/couponsRequest body:
{
"name": "Summer 20% off",
"discountType": "PercentOff",
"discountValue": 20,
"discountCurrency": null,
"duration": "Once",
"durationInMonths": null,
"maxRedemptionsPerCode": 100,
"expiresAt": "2026-12-31T23:59:59Z",
"allowAffiliateSelfMint": false,
"overridesClickAttribution": true
}Validation:
discountTypeisPercentOff(discountValue1-100) orAmountOff(discountValuepositive cents,discountCurrencyrequired ISO 4217).durationisOnce|Repeating|Forever. WhenRepeating,durationInMonthsis required (1-94).expiresAtmust be in the future when set.maxRedemptionsPerCode≥ 1 when set.
Response: 201 Created with the full campaign coupon record including stripeCouponId.
curl -X POST https://api.rekomi.com/api/v1/campaigns/$PROGRAM_ID/coupons \
-H "Authorization: Bearer $REKOMI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer 20% off",
"discountType": "PercentOff",
"discountValue": 20,
"duration": "Once",
"allowAffiliateSelfMint": false,
"overridesClickAttribution": true
}'import Rekomi from "@rekomi/sdk";
const r = new Rekomi(process.env.REKOMI_API_KEY!);
const coupon = await r.coupons.createForCampaign(programId, {
name: "Summer 20% off",
discountType: "PercentOff",
discountValue: 20,
duration: "Once",
allowAffiliateSelfMint: false,
overridesClickAttribution: true,
});List, update, archive, delete
GET /api/v1/campaigns/{programId}/coupons
GET /api/v1/campaigns/{programId}/coupons/{id}
PATCH /api/v1/campaigns/{programId}/coupons/{id} # editable: name, toggles only
POST /api/v1/campaigns/{programId}/coupons/{id}/archive
DELETE /api/v1/campaigns/{programId}/coupons/{id} # only when no per-affiliate codes existPATCH accepts only name, allowAffiliateSelfMint, overridesClickAttribution. Discount semantics are immutable per Stripe's rules. Archive sets isActive=false, deactivates all child promotion codes in Stripe, and marks them revoked locally.
Generate a per-affiliate coupon
POST /api/v1/affiliates/{affiliateId}/couponsRequest body:
{
"campaignCouponId": "c0upon01-...",
"code": "SARAH20"
}campaignCouponIdis required. The campaign coupon must belong to the same program as the affiliate.codeis optional. When null, Rekomi generates an 8-character Crockford-style code (no0/O,1/I/Lfor transcribability). When set, must match^[A-Z0-9_-]{3,32}$.- Codes are unique per organization. Duplicates return
422 code_already_taken.
Response: 201 Created with the new affiliate coupon record including stripePromotionCodeId.
List + revoke
GET /api/v1/affiliates/{affiliateId}/coupons
DELETE /api/v1/affiliates/{affiliateId}/coupons/{id} # revokes (deactivates in Stripe + marks inactive)Affiliate self-mint
When the brand sets allowAffiliateSelfMint=true on a campaign coupon, the affiliate can mint their own vanity codes from /a:
POST /api/me/coupons/mintRequest body:
{
"campaignCouponId": "c0upon01-...",
"code": "SARAH-SPRING"
}The endpoint runs under affiliate-side auth (Clerk JWT, no org context). The caller must belong to the same program the campaign coupon targets, be Approved status, and the vanity code must satisfy the regex above.
Attribution behavior
On every Stripe invoice.paid or customer.subscription.created event, Rekomi extracts discounts[].promotion_code from the payload. If the promotion code maps to one of your AffiliateCoupon rows:
overridesClickAttribution=true(default): attribute to the coupon's owning affiliate even when a click cookie exists.Conversion.attributionMethod = "coupon".overridesClickAttribution=false: check metadata + customer history first. If those don't credit anyone, fall back to the coupon owner.attributionMethod = "metadata_or_customer"or"coupon"depending on which won.- Both click + coupon point to the same affiliate:
attributionMethod = "coupon_with_metadata".
The conversion row carries affiliateCouponId pointing at the redeemed coupon. Refund cascade uses this to decrement redemptionCount and lastRedeemedAt is also bumped on every successful conversion.
Free / 100%-off coupon signups become leads
When a referred visitor redeems a fully discounting (100%-off) coupon, the subscription is created but no paid invoice ever fires. Rekomi records that signup as a lead (a LeadReward lead-only row, no commission), credited to the affiliate who owns the code, so it appears in your funnel even if the customer never pays. The coupon's redemption count still increments, and if the customer later starts paying, that sale is matched back to the lead and commissioned. See Track leads and signups.
Refunds
When Stripe fires charge.refunded, Rekomi:
- Finds the conversion by
payment_intent_idorcharge_id. - Inserts a negative
SubscriptionEvent(reverses commission). - Sets
Conversion.status = RefundedandrefundedAt. - If
affiliateCouponIdis set, decrements the coupon'sredemptionCount(guarded against negative; partial-refund replays don't drive the counter below zero). - Emits
conversion.refundedwebhook.
Webhook events
See the Webhooks reference. Four events live in the affiliate_coupon.* family: created, updated, deactivated, deleted. Subscribe to all of them with affiliate_coupon.* or pick specific events at endpoint creation.
Rate limits
The campaign coupon and per-affiliate coupon endpoints share the standard authenticated rate-limit bucket (600 req/min per key; see Rate limits). Stripe-side rate limits apply independently; bulk-minting more than a few codes per second can return 429, Rekomi retries idempotently using a Rekomi-side stable id, so a retry from your side is safe.