Skip to main content
When a customer clicks Cancel in the billing widget, Cuprice can show them an offer first: a discount, a move down one plan, or a pause. You choose the offers in the project’s Integration → Cancel flow settings. Cuprice never touches your payment service. When the customer accepts an offer, Cuprice sends your server a signed webhook. Your server applies the offer in Stripe, Paddle or whatever you use, and tells Cuprice it did. Offers appear only when:
  • the widget has a v2 subscriber token for this customer (below), and
  • the project has a webhook URL in Integration → Webhooks.
Without either, Cancel goes straight to your data-cuprice-manage-url, as before.
Each customer gets offers at most once per cooldown (6 months by default). Customers on yearly billing are only offered the cheaper plan. Nothing is offered while an accepted discount or pause is still running.

1. Pass a v2 subscriber token

The v2 token tells Cuprice who the customer is, which plan they are on and how they pay, signed with your project’s SDK secret so it can’t be edited in the browser. Mint it on your server and put it in the widget container.
Pass a v2 token only for a subscription that is active and not already set to cancel. You know that; Cuprice doesn’t.
Node.js
To try it without writing code, POST /api/v1/projects/:slug/sdk with { "subscriberId", "plan", "interval", "amount" } returns a subscriberTokenV2 valid for 24 hours.

2. Add your webhook URL

In the project sidebar, open Integration → Webhooks and save an https:// URL. Cuprice creates a signing secret (whsec_…) on the first save. Copy it to your server. Send test event sends a retention.test event and shows the result. Addresses on private or local networks are refused.

3. Handle the webhook

Cuprice sends a POST with a JSON body:
Exactly one of discount, downgrade and pause is set, matching kind: A test event has the same envelope, with "type": "retention.test" and "data": { "test": true }. Headers:

Verify the signature

Check the signature against the raw request body, before parsing it, and reject anything more than 5 minutes old.
Node.js

Reply

Cuprice waits up to 5 seconds for your reply, and the customer sees the result right away: If applying the offer takes longer than 5 seconds, answer 202 right away and confirm through the API when you’re done.

Retries and idempotency

Every attempt for an event carries the same id. Store the ids you have applied and ignore repeats. A notice that wasn’t received is retried about 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours later: 6 attempts in all. If every attempt fails, the project owner gets one email, and Integration → Webhooks lists the delivery with a Resend button that sends the same event again. If the project’s last 3 accepted offers all failed to deliver, the widget stops showing offers until a delivery or a test event succeeds.

Confirm later, or catch up

Confirm an offer you answered with “received”, or correct an earlier answer. The latest answer counts. Use an API key with write access.
status is applied or rejected, with an optional note of up to 500 characters. Sending the same answer again changes nothing ("changed": false). List the offers still waiting for your answer, with the same fields as the webhook’s data:
partner_status can also be applied or rejected.
The JavaScript SDK’s older cancel flow (/api/rescue/trigger, /api/rescue/outcome and /api/rescue/pause) still works, but it only records what the customer picked. Use the billing widget with a v2 token for offers that apply.