Scope
How to configure Cancel Page in Chargebee Retention via webhook?
What is the list of event types in Chargebee Retention?
How to parse webhook content when using a framework in Chargebee Retention?
Summary
Webhooks enable Chargebee Retention to call a script on your server when an event has occurred in the Chargebee Retention cancel experience. Webhook events can be used to trigger an action in your billing system or activate another internal workflow. You can configure this from Settings > Alerts & Webhooks, scroll to the bottom, and click Add.
- Implementing webhooks with Chargebee Retention
- Event Types
- HTTP Response Code
- Retry Policy
- Using the DRF (Django Rest Framework) to parse Chargebee Retention webhooks
A note about your endpoint's URL and redirects:
Chargebee Retention's webhook events will not redirect if your endpoint returns a 3xx HTTP redirect code. To ensure your endpoint's URL is correct you should perform the following test in your terminal.
curl -v https://example.com/endpoint -XPOST
e.g. https://example.com/endpoint redirecting to https://www.example.com/endpoint would be recorded as a failure. Chargebee Retention events would need to be pointed to https://www.example.com/endpoint in this example
Solution
Implementing webhooks with Chargebee
public static String signHmacSha1(String message, String key) {
try {
// Get an hmac_sha1 key from the raw key bytes
byte[] keyBytes = key.getBytes(StandardCharsets.UTF-8);
SecretKeySpec signingkey = new SecretKeySpec(keyBytes, "HmacSHA1");
// Get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingkey);
// Compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(message.getBytes(StandardCharsets.UTF-8));
// Convert raw bytes to Hex
byte[] hexBytes = new Hex().encode(rawHmac);
// Convert array of Hex bytes to a String
return new String(hexBytes, Standard Charsets.UTF_8);
} catch (Exception e) {
throw new IllegalStateException("Unable to sign message", e);
}
}
Event Types
There are twelve event types that can be individually enabled to trigger a POST request for any Chargebee Retention app. Check out our offer creation help doc for a review of offer types and actions.
Trigger | Description |
Page loaded | Any visit to a cancel experience including page refreshes. |
Modal Opened | Viewed a modal offer |
Deflect | Left the page |
Cancel | Clicked cancel |
Send email | Accepted an offer on the send message modal |
Link | Left the page via a URL |
Intercom chat | Initiated an intercom chat |
Accepted Offer | Accepted an offer |
Save | Previously deflected user did not cancel (30 days by default) |
Nevermind | Clicked nevermind |
Accepted LA Offer | Accepted a Loss Aversion Offer |
Accepted Modal Offer | Accepted a Modal Offer |
Root level
"url": "https://www.example.com/endpoint",
"delivery_attempts": 1,
"created_at": "1970-01-01T00:00:00Z",
"subscription_id": "1234567890",
"first_sent_at": "1970-01-01T00:00:00Z",
"id": "a7467d71-92c4-4a2d-92bb-ec315bbbb082",
"type": "event"
The data object
Nested within the data object are several useful parameters as well as the survey, offer, browser context, and fields from the JS snippet.
"data": {
"type": "link",
/* the type of event that was triggered. possible values include page_loaded, deflect, cancel, send_email, link, chat, offer, save and watch_list */
"id": "2955d62b-3b2a-46ce-ad83-204c9b99d689",
"app_id": "1234567890",
// your app within Brightback
"session_id": "abcde12345",
//unique cancel session id
"name": "10_off.sixty_forty_column.217648e7",
//the name of modal or page when event was triggered
"timestamp": "1970-01-01T00:00:00Z",
"survey": {...},
"offer": {...},
"fields": {...},
"context": {...},
},
Values under the data.survey object
The survey key holds answers to survey questions if they were selected at the time of accepting an offer or during cancellation.
"survey": {
"reason_for_leaving": [
{
"name": "reason_for_leaving",
"value": "customer_service_was_unsatisfactory.1928377447",
"tier": "tier0", //legacy
"lives_on": "tier0" //legacy
}
],
"competition": "None",
"sentiment": 10,
"feedback": "Brightback is great!",
"confirmation": true,
"selected_reason": "customer_service_was_unsatisfactory.1928377447",
//unique reason key
"display_reason": "Customer service was unsatisfactory",
//reason shown to canceller
"brightback_reason": "bad_customer_service"
//Brightback internal reason key
},
/* additionally a custom reason key can be added to align with your internal tracking */
Values under data.offer object
The offered key holds information relative to the modal that was displayed if the CTA in an offer is clicked.
"offer": {
"name": "10_off.1731427124",
//unique name of the offer established when the offer was created
"display_name": "$10 Off",
//mutable name of the offer in the experience manager
"type": "$",
//offer type chosen during offer creation
"category": "Discounts"
//category of offer chosen during offer creation
},<br>
Values under data.context object
The context key contains the browser context information from the client.
"context": {
"ip": "1.1.1.1",
"locale": "en-US",
"timezone": "America/Los_Angeles",
"user_agent": "Mozilla/5.0 ...",
"url": "https://cancel.example.com/example/cancel/abcde12345",
},
Values under data.fields object
The fields key contains fields passed through the snippet including custom as well as standard fields that have been mapped in.
The following code block shows a small sample of a typical payload nested in the fields object.
"fields": {
"standard.Owner Email": "jane@brightback.com",
"standard.Owner First Name": "Jane",
"standard.Owner Last Name": "Brighteyes",
//Brightback standard fields if populated and mapped.
"cancel.save_return_url": "https://www.example.com/return",
"cancel.app_id": "1234567890",
"cancel.account.created_at": "1970-01-01T00:00:00Z",
"cancel.account.plan": "Premium",
"cancel.account.plan_term": "monthly",
"cancel.account.internal_id": "abcd123",
//original payload from the JS snippet
"cancel.custom.activity": "none",
"cancel.custom.emails": "500",
"cancel.custom.offer_eligible": true,
//custom fields from the JS snippet
"cancel.context.locale": "en-US",
"cancel.context.timezone": "America/Los_Angeles",
"cancel.context.user_agent": "Mozilla/5.0 ...",
//browser context for cancel session
}
By default we consider responses in the 2xx's to be a success.
3xx, We do not redirect but we will retry.
4xx, 5xx (not including 410, 422, and 429): Are treated as failed and will be retried.
Retry Policy:
We try the endpoint twice on network failures or 4xx or 5xx (excluding 410 or 422). After the second failure, we begin health checks with OPTIONS on the endpoint looking for a successful (2xx) response with HTTP. We continue doing these health checks with exponential backoff for up to 24 hours. When we receive a successful response to a health check, we will retry the original post. This cycle continues for up to 24 hours in total.
410/Gone: We will delete your webhook and no longer retry.
422/Unprocessable entity: We will not retry but leave the webhook active.
429/Throttling: Future placeholder for throttling (not implemented yet)
In the event you receive an event and do not wish to process it or respond with a 2xx response, please respond with code 422 so you do not trigger our retry policy.
Parsing Webhook content when using a framework such as DRF which alters Webhook content
parser_classes = [FileUploadParser]
file_obj = request.data['file']