Update a subscription product’s content. If content has changed, a new immutable version is created automatically. If the content is identical to the current version, no new version is created.
POST /v1/actions/subscription-product/update-product
Authentication: API Key
Request Body
Field Type Required Description idstring Yes Product ID (PROD_xxx format) namestring No Updated product name (max 64 chars) billingPeriodstring No weekly, monthly, quarterly, or yearlypricesobject No Updated multi-currency pricing descriptionstring | null No Updated description (pass null or "" to clear) mediaarray No Updated media successUrlstring | null No Updated redirect URL (max 512 chars, must be a valid http(s) URL; pass null or "" to clear) metadataobject No Updated metadata (may include trialDays)
Example Request
SDK
TypeScript (fetch)
Java
Python
Go
Rust
C
C++
cURL
wget
const { product } = await client . subscriptionProducts . update ({
id: "PROD_3F7H2J5L8N1Q4S6U" ,
name: "Pro Plan v2" ,
billingPeriod: BillingPeriod . Monthly ,
prices: {
USD: { amount: "39.00" , taxIncluded: false , taxCategory: TaxCategory . SaaS },
EUR: { amount: "36.00" , taxIncluded: false , taxCategory: TaxCategory . SaaS },
},
metadata: { trialDays: 7 },
});
// Uses callApi() helper from authentication.mdx
const result = await callApi ( "POST" , "/v1/actions/subscription-product/update-product" , {
id: "PROD_3F7H2J5L8N1Q4S6U" ,
name: "Pro Plan v2" ,
billingPeriod: "monthly" ,
prices: {
USD: { amount: "39.00" , taxIncluded: false , taxCategory: "saas" },
EUR: { amount: "36.00" , taxIncluded: false , taxCategory: "saas" },
},
metadata: { trialDays: 7 },
});
console . log ( result . data );
// Uses callApi() helper from authentication.mdx
String body = """
{"id":"PROD_3F7H2J5L8N1Q4S6U","name":"Pro Plan v2","billingPeriod":"monthly","prices":{"USD":{"amount":"39.00","taxIncluded":false,"taxCategory":"saas"},"EUR":{"amount":"36.00","taxIncluded":false,"taxCategory":"saas"}},"metadata":{"trialDays":7}}""" ;
String response = callApi ( "POST" , "/v1/actions/subscription-product/update-product" , body);
System . out . println (response);
# Uses call_api() helper from authentication.mdx
result = call_api( "POST" , "/v1/actions/subscription-product/update-product" , {
"id" : "PROD_3F7H2J5L8N1Q4S6U" ,
"name" : "Pro Plan v2" ,
"billingPeriod" : "monthly" ,
"prices" : {
"USD" : { "amount" : "39.00" , "taxIncluded" : False , "taxCategory" : "saas" },
"EUR" : { "amount" : "36.00" , "taxIncluded" : False , "taxCategory" : "saas" },
},
"metadata" : { "trialDays" : 7 },
})
print (result[ "data" ])
// Uses callAPI() helper from authentication.mdx
body := map [ string ] interface {}{
"id" : "PROD_3F7H2J5L8N1Q4S6U" ,
"name" : "Pro Plan v2" ,
"billingPeriod" : "monthly" ,
"prices" : map [ string ] interface {}{
"USD" : map [ string ] interface {}{ "amount" : "39.00" , "taxIncluded" : false , "taxCategory" : "saas" },
"EUR" : map [ string ] interface {}{ "amount" : "36.00" , "taxIncluded" : false , "taxCategory" : "saas" },
},
"metadata" : map [ string ] interface {}{ "trialDays" : 7 },
}
result , err := callAPI ( "POST" , "/v1/actions/subscription-product/update-product" , body )
if err != nil {
log . Fatal ( err )
}
fmt . Println ( string ( result ))
// Uses call_api() helper from authentication.mdx
let body = serde_json :: json! ({
"id" : "PROD_3F7H2J5L8N1Q4S6U" ,
"name" : "Pro Plan v2" ,
"billingPeriod" : "monthly" ,
"prices" : {
"USD" : { "amount" : "39.00" , "taxIncluded" : false , "taxCategory" : "saas" },
"EUR" : { "amount" : "36.00" , "taxIncluded" : false , "taxCategory" : "saas" }
},
"metadata" : { "trialDays" : 7 }
});
let result = call_api ( "POST" , "/v1/actions/subscription-product/update-product" , & body ) . await ? ;
println! ( "{}" , result );
/* Uses call_api() helper from authentication.mdx */
const char * body = "{ \" id \" : \" PROD_3F7H2J5L8N1Q4S6U \" , \" name \" : \" Pro Plan v2 \" , \" billingPeriod \" : \" monthly \" , \" prices \" :{ \" USD \" :{ \" amount \" : \" 39.00 \" , \" taxIncluded \" :false, \" taxCategory \" : \" saas \" }, \" EUR \" :{ \" amount \" : \" 36.00 \" , \" taxIncluded \" :false, \" taxCategory \" : \" saas \" }}, \" metadata \" :{ \" trialDays \" :7}}" ;
char * response = call_api ( "POST" , "/v1/actions/subscription-product/update-product" , body);
printf ( " %s \n " , response);
free (response);
// Uses callApi() helper from authentication.mdx
std ::string body = R"({"id":"PROD_3F7H2J5L8N1Q4S6U","name":"Pro Plan v2","billingPeriod":"monthly","prices":{"USD":{"amount":"39.00","taxIncluded":false,"taxCategory":"saas"},"EUR":{"amount":"36.00","taxIncluded":false,"taxCategory":"saas"}},"metadata":{"trialDays":7}})" ;
std ::string response = callApi ( "POST" , "/v1/actions/subscription-product/update-product" , body);
std ::cout << response << std ::endl;
MERCHANT_ID = "MER_2aUyqjCzEIiEcYMKj7TZtw"
TIMESTAMP = $( date +%s )
BODY = '{
"id": "PROD_3F7H2J5L8N1Q4S6U",
"name": "Pro Plan v2",
"billingPeriod": "monthly",
"prices": {
"USD": { "amount": "39.00", "taxIncluded": false, "taxCategory": "saas" },
"EUR": { "amount": "36.00", "taxIncluded": false, "taxCategory": "saas" }
},
"metadata": { "trialDays": 7 }
}'
BODY_HASH = $( echo -n " $BODY " | openssl dgst -sha256 -binary | base64 -w 0 )
CANONICAL_REQUEST = "POST
/v1/actions/subscription-product/update-product
$TIMESTAMP
$BODY_HASH "
SIGNATURE = $( echo -n " $CANONICAL_REQUEST " | openssl dgst -sha256 -sign private_key.pem | base64 -w 0 )
curl -X POST "https://api.waffo.ai/v1/actions/subscription-product/update-product" \
-H "Content-Type: application/json" \
-H "X-Merchant-Id: $MERCHANT_ID " \
-H "X-Timestamp: $TIMESTAMP " \
-H "X-Signature: $SIGNATURE " \
-d " $BODY "
MERCHANT_ID = "MER_2aUyqjCzEIiEcYMKj7TZtw"
TIMESTAMP = $( date +%s )
BODY = '{
"id": "PROD_3F7H2J5L8N1Q4S6U",
"name": "Pro Plan v2",
"billingPeriod": "monthly",
"prices": {
"USD": { "amount": "39.00", "taxIncluded": false, "taxCategory": "saas" },
"EUR": { "amount": "36.00", "taxIncluded": false, "taxCategory": "saas" }
},
"metadata": { "trialDays": 7 }
}'
BODY_HASH = $( echo -n " $BODY " | openssl dgst -sha256 -binary | base64 -w 0 )
CANONICAL_REQUEST = "POST
/v1/actions/subscription-product/update-product
$TIMESTAMP
$BODY_HASH "
SIGNATURE = $( echo -n " $CANONICAL_REQUEST " | openssl dgst -sha256 -sign private_key.pem | base64 -w 0 )
wget -qO- "https://api.waffo.ai/v1/actions/subscription-product/update-product" \
--header= "Content-Type: application/json" \
--header= "X-Merchant-Id: $MERCHANT_ID " \
--header= "X-Timestamp: $TIMESTAMP " \
--header= "X-Signature: $SIGNATURE " \
--post-data= " $BODY "
Success Response (200)
{
"data" : {
"product" : {
"id" : "PROD_3F7H2J5L8N1Q4S6U" ,
"storeId" : "STO_2D5F8G3H1K4M6N9P" ,
"name" : "Pro Plan v2" ,
"description" : null ,
"billingPeriod" : "monthly" ,
"prices" : {
"USD" : { "amount" : "39.00" , "taxCategory" : "saas" },
"EUR" : { "amount" : "36.00" , "taxCategory" : "saas" }
},
"media" : [],
"successUrl" : null ,
"metadata" : { "trialDays" : 7 },
"status" : "active" ,
"createdAt" : "2026-03-30T10:30:00.000Z" ,
"updatedAt" : "2026-03-30T11:00:00.000Z"
}
}
}
Response Fields
Same as Create Subscription Product response .
Product updates create new immutable versions . Existing subscriptions retain their original version. New signups use the latest version. If the submitted content is identical to the current version, no new version is created.
Errors
Retry policy: Never retry 4xx — fix the request and resubmit. Retry 5xx with exponential backoff (start 5s, max 3 attempts).
Status errors[0].messageWhat it means Recommended handling 400 Missing header: x-context-merchant-idThe merchant context header was not forwarded Fix your SDK configuration 400 Missing or invalid header: x-context-environmentEnvironment header is missing or not test / prod Set X-Environment to test or prod 400 Missing required field: idRequest body did not include id Add id, resubmit 400 Expected format: PROD_xxx, got "X"id is not a valid Short IDUse a PROD_ prefixed Short ID 400 Field name must be a non-empty stringname was provided as an empty stringOmit name to keep current value, or provide a non-empty string 400 Invalid billingPeriodbillingPeriod is not one of weekly / monthly / quarterly / yearlyUse one of the four allowed values 400 Field prices must be a non-empty objectprices was provided but emptyOmit prices to keep current value, or include at least one currency 400 Invalid currency code: "X". Must be 3 uppercase letters (e.g., "USD", "EUR", "JPY")A key in prices is not a valid ISO 4217 code Use 3 uppercase letters 400 Invalid amount for X: "Y". Must be a positive number string (e.g., "9.99", "1000")Amount string couldn’t be parsed Use a positive decimal string 400 Message contains has no version The target environment (test or prod) has no version yet Create a version in this environment first (publish from test, or create in test) 401 UnauthorizedAuthentication failed Verify API key, timestamp, and signature 404 Product not found / Current version not foundProduct ID does not exist (or current version missing) Verify the product ID 500 Internal server errorTransient downstream failure Retry with exponential backoff (start 5s, max 3 attempts)