curl --request POST \
--url https://api.growi.io/api/public/v1/affiliate_sales \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"order_id": "<string>",
"total": 123,
"subtotal": 123,
"platform": "website",
"campaign_affiliate_id": "<string>",
"growi_user_id": 123,
"growi_campaign_id": 123,
"occurred_at": "2023-11-07T05:31:56Z",
"due_at": "2023-11-07T05:31:56Z",
"customer_email": "jsmith@example.com",
"customer_name": "<string>",
"visitor_uid": "<string>",
"sale_type": "one_time",
"commission": 123,
"sub_id": "instagram"
}
'import requests
url = "https://api.growi.io/api/public/v1/affiliate_sales"
payload = {
"order_id": "<string>",
"total": 123,
"subtotal": 123,
"platform": "website",
"campaign_affiliate_id": "<string>",
"growi_user_id": 123,
"growi_campaign_id": 123,
"occurred_at": "2023-11-07T05:31:56Z",
"due_at": "2023-11-07T05:31:56Z",
"customer_email": "jsmith@example.com",
"customer_name": "<string>",
"visitor_uid": "<string>",
"sale_type": "one_time",
"commission": 123,
"sub_id": "instagram"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
order_id: '<string>',
total: 123,
subtotal: 123,
platform: 'website',
campaign_affiliate_id: '<string>',
growi_user_id: 123,
growi_campaign_id: 123,
occurred_at: '2023-11-07T05:31:56Z',
due_at: '2023-11-07T05:31:56Z',
customer_email: 'jsmith@example.com',
customer_name: '<string>',
visitor_uid: '<string>',
sale_type: 'one_time',
commission: 123,
sub_id: 'instagram'
})
};
fetch('https://api.growi.io/api/public/v1/affiliate_sales', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.growi.io/api/public/v1/affiliate_sales",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'order_id' => '<string>',
'total' => 123,
'subtotal' => 123,
'platform' => 'website',
'campaign_affiliate_id' => '<string>',
'growi_user_id' => 123,
'growi_campaign_id' => 123,
'occurred_at' => '2023-11-07T05:31:56Z',
'due_at' => '2023-11-07T05:31:56Z',
'customer_email' => 'jsmith@example.com',
'customer_name' => '<string>',
'visitor_uid' => '<string>',
'sale_type' => 'one_time',
'commission' => 123,
'sub_id' => 'instagram'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.growi.io/api/public/v1/affiliate_sales"
payload := strings.NewReader("{\n \"order_id\": \"<string>\",\n \"total\": 123,\n \"subtotal\": 123,\n \"platform\": \"website\",\n \"campaign_affiliate_id\": \"<string>\",\n \"growi_user_id\": 123,\n \"growi_campaign_id\": 123,\n \"occurred_at\": \"2023-11-07T05:31:56Z\",\n \"due_at\": \"2023-11-07T05:31:56Z\",\n \"customer_email\": \"jsmith@example.com\",\n \"customer_name\": \"<string>\",\n \"visitor_uid\": \"<string>\",\n \"sale_type\": \"one_time\",\n \"commission\": 123,\n \"sub_id\": \"instagram\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.growi.io/api/public/v1/affiliate_sales")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"order_id\": \"<string>\",\n \"total\": 123,\n \"subtotal\": 123,\n \"platform\": \"website\",\n \"campaign_affiliate_id\": \"<string>\",\n \"growi_user_id\": 123,\n \"growi_campaign_id\": 123,\n \"occurred_at\": \"2023-11-07T05:31:56Z\",\n \"due_at\": \"2023-11-07T05:31:56Z\",\n \"customer_email\": \"jsmith@example.com\",\n \"customer_name\": \"<string>\",\n \"visitor_uid\": \"<string>\",\n \"sale_type\": \"one_time\",\n \"commission\": 123,\n \"sub_id\": \"instagram\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.growi.io/api/public/v1/affiliate_sales")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"order_id\": \"<string>\",\n \"total\": 123,\n \"subtotal\": 123,\n \"platform\": \"website\",\n \"campaign_affiliate_id\": \"<string>\",\n \"growi_user_id\": 123,\n \"growi_campaign_id\": 123,\n \"occurred_at\": \"2023-11-07T05:31:56Z\",\n \"due_at\": \"2023-11-07T05:31:56Z\",\n \"customer_email\": \"jsmith@example.com\",\n \"customer_name\": \"<string>\",\n \"visitor_uid\": \"<string>\",\n \"sale_type\": \"one_time\",\n \"commission\": 123,\n \"sub_id\": \"instagram\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": 1947457,
"external_id": "ORD-12345",
"platform": "website",
"status": "unpaid",
"occurred_at": "2025-09-30T23:55:29.000Z",
"total": 9999,
"commission": 500,
"currency": "usd",
"sub_id": "instagram"
}
}{
"error": "<string>",
"message": "<string>"
}{
"error": "Order ID is required."
}Create Affiliate Sale
Creates a conversion and attributes it to a campaign affiliate. Defaults to platform=website; pass platform=ios or android for mobile app sales. Idempotent: sending the same order_id for the same platform returns the existing sale.
curl --request POST \
--url https://api.growi.io/api/public/v1/affiliate_sales \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"order_id": "<string>",
"total": 123,
"subtotal": 123,
"platform": "website",
"campaign_affiliate_id": "<string>",
"growi_user_id": 123,
"growi_campaign_id": 123,
"occurred_at": "2023-11-07T05:31:56Z",
"due_at": "2023-11-07T05:31:56Z",
"customer_email": "jsmith@example.com",
"customer_name": "<string>",
"visitor_uid": "<string>",
"sale_type": "one_time",
"commission": 123,
"sub_id": "instagram"
}
'import requests
url = "https://api.growi.io/api/public/v1/affiliate_sales"
payload = {
"order_id": "<string>",
"total": 123,
"subtotal": 123,
"platform": "website",
"campaign_affiliate_id": "<string>",
"growi_user_id": 123,
"growi_campaign_id": 123,
"occurred_at": "2023-11-07T05:31:56Z",
"due_at": "2023-11-07T05:31:56Z",
"customer_email": "jsmith@example.com",
"customer_name": "<string>",
"visitor_uid": "<string>",
"sale_type": "one_time",
"commission": 123,
"sub_id": "instagram"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
order_id: '<string>',
total: 123,
subtotal: 123,
platform: 'website',
campaign_affiliate_id: '<string>',
growi_user_id: 123,
growi_campaign_id: 123,
occurred_at: '2023-11-07T05:31:56Z',
due_at: '2023-11-07T05:31:56Z',
customer_email: 'jsmith@example.com',
customer_name: '<string>',
visitor_uid: '<string>',
sale_type: 'one_time',
commission: 123,
sub_id: 'instagram'
})
};
fetch('https://api.growi.io/api/public/v1/affiliate_sales', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.growi.io/api/public/v1/affiliate_sales",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'order_id' => '<string>',
'total' => 123,
'subtotal' => 123,
'platform' => 'website',
'campaign_affiliate_id' => '<string>',
'growi_user_id' => 123,
'growi_campaign_id' => 123,
'occurred_at' => '2023-11-07T05:31:56Z',
'due_at' => '2023-11-07T05:31:56Z',
'customer_email' => 'jsmith@example.com',
'customer_name' => '<string>',
'visitor_uid' => '<string>',
'sale_type' => 'one_time',
'commission' => 123,
'sub_id' => 'instagram'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.growi.io/api/public/v1/affiliate_sales"
payload := strings.NewReader("{\n \"order_id\": \"<string>\",\n \"total\": 123,\n \"subtotal\": 123,\n \"platform\": \"website\",\n \"campaign_affiliate_id\": \"<string>\",\n \"growi_user_id\": 123,\n \"growi_campaign_id\": 123,\n \"occurred_at\": \"2023-11-07T05:31:56Z\",\n \"due_at\": \"2023-11-07T05:31:56Z\",\n \"customer_email\": \"jsmith@example.com\",\n \"customer_name\": \"<string>\",\n \"visitor_uid\": \"<string>\",\n \"sale_type\": \"one_time\",\n \"commission\": 123,\n \"sub_id\": \"instagram\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.growi.io/api/public/v1/affiliate_sales")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"order_id\": \"<string>\",\n \"total\": 123,\n \"subtotal\": 123,\n \"platform\": \"website\",\n \"campaign_affiliate_id\": \"<string>\",\n \"growi_user_id\": 123,\n \"growi_campaign_id\": 123,\n \"occurred_at\": \"2023-11-07T05:31:56Z\",\n \"due_at\": \"2023-11-07T05:31:56Z\",\n \"customer_email\": \"jsmith@example.com\",\n \"customer_name\": \"<string>\",\n \"visitor_uid\": \"<string>\",\n \"sale_type\": \"one_time\",\n \"commission\": 123,\n \"sub_id\": \"instagram\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.growi.io/api/public/v1/affiliate_sales")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"order_id\": \"<string>\",\n \"total\": 123,\n \"subtotal\": 123,\n \"platform\": \"website\",\n \"campaign_affiliate_id\": \"<string>\",\n \"growi_user_id\": 123,\n \"growi_campaign_id\": 123,\n \"occurred_at\": \"2023-11-07T05:31:56Z\",\n \"due_at\": \"2023-11-07T05:31:56Z\",\n \"customer_email\": \"jsmith@example.com\",\n \"customer_name\": \"<string>\",\n \"visitor_uid\": \"<string>\",\n \"sale_type\": \"one_time\",\n \"commission\": 123,\n \"sub_id\": \"instagram\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": 1947457,
"external_id": "ORD-12345",
"platform": "website",
"status": "unpaid",
"occurred_at": "2025-09-30T23:55:29.000Z",
"total": 9999,
"commission": 500,
"currency": "usd",
"sub_id": "instagram"
}
}{
"error": "<string>",
"message": "<string>"
}{
"error": "Order ID is required."
}platform: website. Pass platform: ios or platform: android to create mobile app sales that show up in iOS/Android analytics (same AffiliateSale rows those dashboards already read).
This endpoint is idempotent: sending the same order_id again for the same platform returns the existing sale with a 201 status code, making it safe for webhook handlers and retry logic.
Attribution Methods
You can attribute the sale using one of two methods:- Affiliate Tag (
campaign_affiliate_id): Use the creator’s unique affiliate tag/code - Growi IDs (
growi_user_id+growi_campaign_id): Use the Growi user ID and campaign ID directly
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
order_id | String | Your unique order/transaction identifier | Yes |
total | Integer | Total order amount in cents (e.g., 9999 = $99.99). At least one of total or subtotal is required. | Conditional |
subtotal | Integer | Order subtotal in cents (before shipping/tax). At least one of total or subtotal is required. | Conditional |
currency | String | Currency code: usd, eur, gbp, etc. | Yes |
platform | String | Conversion platform: website (default), ios, or android. Defaults to website if omitted. For ios/android, the organization must have that app connected in Growi. | No |
campaign_affiliate_id | String | Affiliate tag for attribution (required if not using growi_user_id + growi_campaign_id) | Conditional |
growi_user_id | Integer | Growi user ID (required with growi_campaign_id if not using campaign_affiliate_id) | Conditional |
growi_campaign_id | Integer | Growi campaign ID (required with growi_user_id if not using campaign_affiliate_id) | Conditional |
occurred_at | String | ISO 8601 timestamp when the sale occurred. Defaults to current time. | No |
due_at | String | ISO 8601 timestamp when commission becomes due | No |
customer_email | String | Customer’s email address | No |
customer_name | String | Customer’s name | No |
visitor_uid | String | Visitor unique identifier (for tracking purposes) | No |
sale_type | String | Type of sale: one_time or subscription. Default is one_time. | No |
commission | Integer | Override commission amount in cents. If omitted, calculated from campaign rules for the given platform. | No |
sub_id | String | Custom source/sub-ID tag (e.g., instagram, youtube) for platform-level tracking within a single campaign. Returned in sale responses and filterable on the list endpoint. | No |
Request Example
Using affiliate tag (website — default):curl -X POST "https://api.growi.io/api/public/v1/affiliate_sales" \
-H "Authorization: Bearer YOUR_PUBLIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"order_id": "ORD-12345",
"total": 9999,
"currency": "usd",
"campaign_affiliate_id": "SUMMER2025",
"occurred_at": "2025-09-30T12:00:00Z"
}'
curl -X POST "https://api.growi.io/api/public/v1/affiliate_sales" \
-H "Authorization: Bearer YOUR_PUBLIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"order_id": "ORD-67890",
"total": 4999,
"currency": "usd",
"growi_user_id": 12345,
"growi_campaign_id": 16200,
"occurred_at": "2025-09-30T14:30:00Z"
}'
curl -X POST "https://api.growi.io/api/public/v1/affiliate_sales" \
-H "Authorization: Bearer YOUR_PUBLIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"order_id": "ORD-IOS-12345",
"total": 9999,
"currency": "usd",
"platform": "ios",
"campaign_affiliate_id": "SUMMER2025",
"occurred_at": "2025-09-30T12:00:00Z"
}'
Response Example
201 Created - Sale created successfully (or already exists with same order_id + platform):{
"data": {
"id": 1947458,
"external_id": "ORD-12345",
"platform": "website",
"status": "unpaid",
"occurred_at": "2025-09-30T12:00:00.000Z",
"total": 9999,
"commission": 500,
"currency": "usd"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
data | Object | The created affiliate sale object |
data.id | Integer | Unique Growi identifier for the sale |
data.external_id | String | Your order ID (same as the order_id you provided) |
data.platform | String | website, ios, or android (defaults to website) |
data.status | String | Initial status: unpaid |
data.occurred_at | String | ISO 8601 timestamp when the sale occurred |
data.total | Integer | Total order amount in cents |
data.commission | Integer | Commission amount in cents (calculated or overridden) |
data.currency | String | Currency code |
Error Responses
422 Unprocessable Entity - Missing required fields or invalid attribution:{
"error": "Order ID is required."
}
{
"error": "Currency is required."
}
{
"error": "Total or subtotal is required."
}
{
"error": "Platform must be one of: website, ios, android."
}
{
"error": "iOS app not found for this organization."
}
{
"error": "Attribution required: provide campaign_affiliate_id OR both growi_user_id and growi_campaign_id."
}
{
"error": "Campaign affiliate not found."
}
Use Cases
This endpoint is useful for:- Server-Side Conversion Tracking: Report conversions from your backend after a successful checkout
- iOS / Android In-App Purchases: Report mobile payments with
platform: iosorplatform: android(preferred over tracking-events for sales) - Custom Checkout Flows: Integrate with headless commerce or custom payment systems
- Webhook Handlers: Process payment webhooks (idempotent design ensures safe retries)
- CRM Integration: Create conversions when deals close in your CRM
- Manual Attribution: Attribute offline or phone sales to specific creators
- Commission Override: Specify custom commission amounts for special promotions
Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
Your unique order/transaction identifier
Currency code
usd, eur, gbp Total order amount in cents. At least one of total or subtotal is required.
Order subtotal in cents. At least one of total or subtotal is required.
Conversion platform. Defaults to website. Use ios or android for mobile app sales (organization must have that app connected).
website, ios, android Affiliate tag for attribution (required if not using growi_user_id + growi_campaign_id)
Growi user ID (required with growi_campaign_id if not using campaign_affiliate_id)
Growi campaign ID (required with growi_user_id if not using campaign_affiliate_id)
When the sale occurred (ISO 8601)
When commission becomes due
Customer email address
Customer name
Visitor unique identifier
Type of sale
one_time, subscription Override commission amount in cents
Custom source/sub-ID tag (e.g., instagram, youtube) for platform-level tracking within a single campaign
"instagram"
Response
Affiliate sale created successfully
Show child attributes
Show child attributes