Unlock the full potential of our feature and take your skills to the next level! Dive into our Training Center and discover exclusive Best Practice resources that will elevate your implementation strategy. With expert tips and insider knowledge, you'll become a master in no time. Access the links below to learn more and gain a competitive edge.
Ready to get started? Our Training Center is just a click away: here.
*For more information about our Training center, please visit the article here.
Overview and Implementation
Callbacks are JavaScript frontend functions that can be executed after a user performs a specific action on your website using Piano. Some examples of actions you can define callbacks for include:
-
A user completes checkout
-
A user logs in
-
A user closes an offer template
The callback code can be added via multiple options:
-
Directly to your website's code - e.g. if you want to add a callback only to a specific URL
-
In the Composer integration script - if you want to run callbacks universally on your whole website (on any page running Piano)
-
In a Composer Run JS card - if you want to run the callback only for a specific experience
The tp object needs to always be initialized before the callback's code. Learn more in our documentation here. We recommend using callbacks to pass events to external systems, external-events where that is not possible, and finally postMessages to pass data out of the iframe.
Checkout events
checkoutAlreadyPurchased
The checkoutAlreadyPurchased event fires when a user already has access to the term.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "checkoutAlreadyPurchased", function(params) {
console.log("RID:", params.rid);
// Add your code here
}
]);
checkoutComplete
The checkoutComplete event fires when the conversion process has completed. Clients often use this event in order to redirect a user after checkout.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "checkoutComplete", function(conversion){
// Your code after successful purchase
}]);
It will return a conversion object with this structure:
{
chargeAmount(number): The amount the user was charged ,
chargeCurrency(string): The type of currency used (e.g. "USD"),
cookie_domain(string): The domain where the Piano cookie was placed ,
email(string): The user's email address ,
expires(string): Timestamp of the access expiration ,
paymentId(string): A unique transaction ID ,
promotionId(string): If a promo code was used, this will be the ID of promotion ,
rid(string): The resource ID ,
startedAt(string): When access started ,
termConversionId(string): The ID of the term conversion ,
termId(string): The term ID ,
token_list(string): Encrypted access token list (stored in Piano's __tac cookie) ,
uid(string): The user ID ,
user_token(string): A user token from the user provider (could be from Gigya, Piano ID, Piano Accounts, or userRef) ,
}
Here's a version of the conversion object with example values:
{
"chargeAmount": 1.99,
"chargeCurrency": "USD",
"cookie_domain": "yoursite.com",
"email": "john.smith@email.com",
"expires": 1456245899,
"paymentId": "UPLYAMDU97J2",
"promotionId": null,
"rid": "RPUV65H",
"startedAt": "2016-02-22T16:44:59.777+0000",
"termConversionId": "TCS0ELTCW3LR",
"termId": "TMP9UFZB97RJ",
"token_list": "{jax}1xc23NBegZededpBk5n0AZ...",
"uid": "O6EDJtQiZz",
"user_token": "{jax}hoZoQRENxtZWaZF2ekHT_b...",
}
checkoutClose
The checkoutClose event fires when the modal/lightbox closes. Because the event object includes information about whether or not a purchase occurred before the modal closed, you could hook onto the checkoutClose event to present the user with an abandonment prevention offer.
Here's an example of the JavaScript configuration for such a situation:
tp.push(["addHandler", "checkoutClose", function(event){
// The event object contains information about the state of closed modal
switch (event.state){
case 'checkoutCompleted':
// User completed the purchase and now has access
// Usually it's a good practice to reload the page
break;
case 'alreadyHasAccess':
// User already has access
// This state could be a result of user logging in during checkout process
// Usually it's a good practice to reload the page here as well
break;
case 'voucherRedemptionCompleted':
// User redeemed a gift voucher
// Normally gift redemption happens on a landing page,
// so logically it makes sense to redirect user to a home page after this
break;
case 'close':
// User did not complete the purchase and simply closed the modal
}
}]);
checkoutCustomEvent
The checkoutCustomEvent callback can be used to track custom events within the checkout process. When editing your offer templates, any element with the external-event directive will trigger a custom event. Custom events are typically triggered on click. Since, as the name implies, custom events are customizable, you can use them for whatever purpose you want and create as many custom events as you need.
Below is a sample snippet from an Offer Template with an external-event directive:
<span external-event="login">Click here to login</span>
And here is the JavaScript configuration to listen for this custom event:
tp.push(["addHandler", "checkoutCustomEvent", function(event){
switch(event.eventName) {
case "login":
window.location.href = "/login";
break;
}
} ]);
See here for more information on the external-event directive.
checkoutError
The checkoutError event fires if a terminal error occurs during the checkout. For example, when the offerId or aid is invalid or the offer is empty and doesn't contain any terms.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "checkoutError", function(errorData){
alert("Error! " + errorData.message);
}]);
checkoutLoaded
The checkoutLoaded event fires on checkout load. Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "checkoutLoaded", function(params) {
console.log("Height:", params.height);
console.log("IFrame ID:", params.iframeId);
// Add your additional code here
}
]);
checkoutSelectChangeOption
The checkoutSelectChangeOption event fires when the change option is selected within an upgrade offer. Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "checkoutSelectChangeOption", function(params) {
console.log("Destination Term Name:", params.termName);
console.log("Destination Term ID:", params.termId);
console.log("Offer ID:", params.offerId);
// Add your additional code here
}
]);
checkoutSelectTerm
The checkoutSelectTerm event fires when a user selects a term within an offer. Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "checkoutSelectTerm", function(termDetails, iframeId){
// Your code after the user selects a term
} ]);
where termDetails is an object that contains the properties termId, termName, resourceId, and resourceName. The iframeId returns the HTML ID that you gave to that iframe.
Please note, that the user needs to be logged in for this callback to be triggered. If the user is not yet logged in when selecting a term within the offer, the callback will trigger after the login is successful (or a registration completed).
checkoutStateChange
The checkoutStateChange event fires when the state of checkout is changed for the user (e.g. the user enters the payment components screen). Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "checkoutStateChange", function(event){
// Your code after the checkout state changes
switch (event.stateName) {
case "state2":
// The second state of checkout, where users input their payment details.
break;
}
} ]);
The possible values of stateName with their descriptions are as follows:
-
offer: The initial offer screen wherein the user can select a term. -
state1: The first state of checkout, where the list of terms is presented. -
state2: The second state of checkout, where users input their payment details. -
auth: The authentication screen. -
alreadyHasAccess: Displayed when a user tries to checkout on a resource they already have access to. -
lockedPromoCode: The screen displayed when a user selects a term that's locked by a promotion. -
externalVerification: The screen where a user validates data against a third party's system. -
printAddress: The screen where a user inputs their print address if the term is configured to collect it. -
confirmation: The screen followingstate2in case the user needs to confirm the tax details. -
giftParams: Displayed when the user needs to input the recipient and a message in the case of a gift purchase. -
redemption: Where the user inputs a gift code if they have received one. -
redemptionCodeApplied: The screen following the gift code input and need to confirm the gift redemption.
completeUpgradePurchase
The completeUpgradePurchase event fires when a user completes an upgrade. Here's the JavaScript configuration to listen for this event:
function onCompleteUpgradePurchase(params) {
// params = { termConversionId: 'term conversion id'}
}
tp.push(["addHandler", "completeUpgradePurchase", onCompleteUpgradePurchase]);
It will return an object with this structure:
{termConversionId: termConversionId}
resize
The resize event fires on iframe resizing. Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "resize", function(params) {
console.log("Height:", params.height);
console.log("Width:", params.width);
console.log("IFrame ID:", params.iframeId);
// Add your additional code here
}
]);
showOffer
The showOffer event fires when a user is presented with an offer. This callback does not fire when a Show Template card is used in Composer, it is fired only for the Show Offer card. Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "showOffer", function(offerParams){
// Your code after offer has been shown
} ]);
The offerParams attribute passed to the callback function has the same fields as the parameters required for showing an offer. Those offer parameters are discussed here.
showTemplate
The showTemplate event fires when a user is presented with a template. This callback does not fire when a Show Offer card is used in Composer, it is fired only for the Show Template card. Here's the JavaScript configuration to listen for this event:
tp.push( [ "addHandler", "showTemplate", function (templateParams) {
// Your code after template has been shown
}]);
The templateParams attribute passed to the callback function contains fields that can be used as parameters required for showing an offer. Those offer parameters are discussed here.
The response object will contain the following information. Please note, that this is a non-exhaustive list of data:
{
additionalParamNames: []
affiliateState: {issuerId: '<website_url>', userId: '<uid>', }
browserId: "WloCSTzqSvq0z3GAa7spEwUsJJhfj8ZDxoywAxi7sUn7d0Rv9YRlMesTld45"
containerSelector: "#inline"
displayMode: "inline"
experienceId: "EX55Y6616QAJ"
iframeUrl: "<iframe_url>"
postMessageUrl: "<postMessage_url>"
showCloseButton: false
templateId: "OTJ1OA5APC6Q"
templateVariantId: null
trackingId: "<tracking_id>"
widget: "template"
}
startCheckout
The startCheckout event fires when a user begins checkout on a term. Here's the JavaScript configuration to listen for this event:
tp.push( [ "addHandler", "startCheckout", function () {
// Your code after checkout has started
}]);
It will return an object that also contains the following information:
{
aid: "<AID>"
debug: "true"
displayMode: "modal"
experienceId: "<Experience_ID>"
formNameByTermId: "{}"
hasLoginRequiredCallback: "true"
lang: "en_US"
offerId: "<Offer_ID>"
showCloseButton: "false"
templateId: "<Template_ID>"
termId: "<Term_ID>"
type: "payment"
upgradeSubscriptionId: ""
url: "<Website_URL>"
userProvider: "piano_id"
userToken: ""
}
submitPayment
The submitPayment event fires when a user submits payment when checking out. Here's the JavaScript configuration to listen for this event:
tp.push( [ "addHandler", "submitPayment", function (data) {
// Your code after payment is submitted
}]);
This will return the data object containing the offerId and a term object that resembles the following:
{
offerId(string): "" // the ID of the offer that includes the purchased term
termObject(string):
allowPromoCodes: false (bool) // is promo codes allowed
billingPlanTable: [{…}] (array) //
chargeAmount: 2.99 (number) // price
chargeCurrency: "EUR" (string) // term currency
chargeDisplayAmount: "EUR2.99" (string) // displayed price
description: "Fixed time pay-what-you-want term for 1 week" (string) // term description
displayLine: "one payment of EUR2.99" (string) // displayed merged description and price
firstPeriod: "1 week" (string) // first period duration
firstRealPrice: "EUR2.99" (string) // price of first period without taxes
firstRealPriceWithTax: "EUR2.99" // price of first period with taxes
forceAutoRenew: false (bool) // term force renewed after end
giftEmailSendEndTime: null (string|date) // send gift email period end date
giftEmailSendStartTime: null (string|date) // send gift email period start date
gstAmount: "EUR0.00" (string) // indian tax amount
gstRate: null (number) // indian tax rate
hasFinishedSales: false (bool) // sales is ended
hasFreeTrial: false (bool) // has trial period
hstAmount: "EUR0.00" (string) // canadian tax amount
hstRate: null (number) // canadian tax rate
isCustomPriceAvailable: true (bool) // custom price available for this term
isPaymentMethodRequired: true (bool) // payment method required
isSubscription: false (bool) // subscription
isZero: false (bool) // free term
name: "Fixed time payment 1w (Resource_2)" (string) // term name
newCustomersOnly: false (bool) // this term could buy only new customers
oneOffPaymentMethods: (3) [{…}, {…}, {…}] (array) // payment methods
originalBillingPlan: null (string) // original billing plan
price: "EUR2.99" (string) // price
pstAmount: "EUR0.00" (string) // provincial tax amount
pstRate: null (number) // provincial tax rate
qstAmount: "EUR0.00" (string) // Quebec sales tax amount
qstRate: null (number) // Quebec sales tax rate
resource: {name: "Resource_2", description: "Resource 2 description", rid: "RNYAHZJ", url: null,
imageUrl: null} (object) // term resources
restrictCheckoutProcess: false (bool) // ???
selected: true (bool) // this term selected
sellDate: null (string|date) // term sale date
sku: null (string) // product ID - A unique, human readable ID for your product. Product IDs are also called SKUs in the Google Play Billing Library.
subscriptionPaymentMethods: (3) [{…}, {…}, {…}] (array) // subscription payment methods
taxAmount: "EUR0.00" (string) // tax amount
taxRate: null (number) // tax rate
termId: "TM8CF2X9HP23" (string) // termId
totalAmount: "EUR2.99" (string) // total price
type: "payment" (string) // term type
withNewCustomerBillingPlan: false (bool) // term for new customers
}
Login and registration events
These events fire also in case the login or registration is triggered when an offer is shown.
loginRequired
The loginRequired event fires when a user is not logged in and attempts to start checkout. If you're using Identity Management or Gigya for user management, you don't need to worry about the loginRequired callback (your user management system will trigger login automatically when you use Piano's login() method in an offer template). For all other user management providers, you will need to implement this callback to redirect the user to your login/registration page or to display your login/registration modal. Once the user is logged in, checkout can continue. For more information on user management integration, please see our guide.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "loginRequired", function(params){
// this is a reference implementation only
// your own custom login/registration implementation would
// need to return the Piano-compatible userRef inside the callback
mysite.showLoginRegistration(function(pianoUserRef) {
tp.push(["setUserRef", pianoUserRef]);
tp.offer.startCheckout(params);
});
// this will prevent the Piano error screen from displaying
return false;
}
loginSuccess
The loginSuccess event fires when a user completes the login process using Identity Management. Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "loginSuccess", function(data){
if (data.source == "PIANOID"){
location.reload(); // this reloads the page after direct login (NOT login inside checkout)
}
}]);
where data is a JSON object containing the following:
-
event: Will always beloginSuccess -
params: Decrypted claims of the JWT token -
registration: Boolean, defines whether the action was due to registration or login. -
source: String, eitherPIANOIDorOFFER, which indicates whether the user signed in by manually initializing registration/login or due to a paywall, respectively -
user_token: The user token.
registrationSuccess
The registrationSuccess event fires when a user completes the registration process using Identity Management. Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "registrationSuccess", function(data){
// code
}]);
where data is a JSON object containing the following:
{
"token": "<token>",
"user": {
"iss": "https://id.piano.io",
"sub": "<user UID>",
"aud": "<AID>",
"login_timestamp": "1676370568119",
"given_name": "<given_name>",
"family_name": "<family_name>",
"email": "<email>",
"email_confirmation_required": false,
"exp": 1676456968,
"iat": 1676370568,
"jti": "TIZV7RmN24rq2ft4",
"passwordType": "password",
"r": true,
"rememberMe": true,
"ls": "ID"
},
"cookie_domain": ".piano.io",
"registration": true,
"extendExpiredAccessEnabled": false,
"source": "PIANOID"
}
The parameter "r" holds a boolean value indicating whether the "Stay logged in" checkbox was selected during registration.
If the registration was initiated through an Offer, the JSON object includes additional parameters: stage and trackingId.
If the user registers via a social network, the "ls" property value contains information about the name of the network used, for example, "GOOGLE" or "FACEBOOK".
Additionally, when using social login for registration, a property called si is added, representing the Social ID/account number from the respective social platform (such as Google, Facebook, etc.). For example:
si: "123456789"
When the user clicks on a registration term from an offer and registers through this offer, the registrationSuccess callback is not fired.
Only the checkoutComplete callback is fired after the user converts on the registration term.
Logout events
logout
The logout event fires when a user logs out using the Identity Management function tp.pianoId.logout().
Here's the JavaScript configuration to listen for this event:
tp.push(['addHandler', 'logout', function(){
/// .... code
}])
If your implementation reloads the page immediately after calling logout, the logout request may not complete successfully. To avoid logout requests failing, trigger any page reload only after logout has finished; either inside the logout() callback or inside the logout event handler.
The logout callback itself does not contain data about the user's email address, e.g. if you'd like to pass this information to any 3rd party software upon logout.
It is possible to save the email address to a cookie or session storage with the function tp.pianoId.getUser().email and in the logout callback, read this data and pass it to the 3rd party vendor.
For example by using this pseudocode:
tp.push(["init", function() {
if(tp.pianoId.getUser() != null)
sessionStorage.setItem("email", tp.pianoId.getUser().email);
tp.push(['addHandler', 'logout', function(){
var email = sessionStorage.getItem("email");
sessionStorage.removeItem("email");
// Your custom script
}])
}]);
Trigger events
experienceExecute
The experienceExecute event fires when an experience fires for a user pageview.
tp.push(["addHandler", "experienceExecute", (e) => {}]);
The event parameters for this callback are as follows:
{
"accessList": [ // list of user accesses (empty list for anonymous)
{
"daysUntilExpiration": -1, // days until access is expired, -1 in case of unlimited access
"expireDate": -1, // expiration date, -1 if unlimited
"resourceName": "Access To Everything!",
"rid": "TPMeteredAccess" // resource id
}
],
"result": { // composer execution result, contains various information, may be used for debugging
"debugMessages": [ // debug messages, empty if "debug mode" is disabled
"[Execute experience (pubId = EXMZ11BB0S96, updateDate = 2017-03-07 12:23:28.0)]",
...
],
"events": [ // list of composer events emitted in composer execution
{
"eventConditions": [...],
"eventExecutionContext": {...},
"eventModuleParams": {...},
"eventParams": {...},
"eventType": "userSegmentTrue"
},
...
],
"experiences": [ // list of executed experiences
{
"id": "EXMZ11BB0S96",
"title": "experience1",
"type": site,
"updateDate": "2017-03-07T08:23:28.000+0000",
"version": 1,
"isLive": true
},
...
]
},
"user": { // information about user
"email": "...",
"firstName": "...",
"lastName": "...",
"uid": "..."
}
}
// not applicable
cardParams: {
"moduleId": "N/A",
"moduleName": "N/A"
}
context: <EventExecutionContext>
meterActive
The meterActive event fires when user is being metered but the meter limit has not yet been reached. Here's an example of hooking onto this event to tell users how many meter views they have left:
tp.push(["addHandler", "meterActive", function(meterData){
alert("You've seen " + meterData.views
+ " out of " + meterData.maxViews
+ " free articles. You have" + meterData.viewsLeft
+ " articles left."
);
}]);
The meterActive event will return the meterData object which contains information about the current meter status. The structure looks like this:
{
meterName(string): The name given the meter in Composer ,
views(integer): The number of metered pageviews a user has consumed within the meter limit ,
totalViews(integer): The number of metered pageviews a user has consumed in total ,
viewsLeft(integer): The number of metered pageviews a user has remaining within the meter limit ,
maxViews(integer): The meter limit (the metered expires after the user reaches this number of pageviews)
}
Here's the meterData object with example values:
{
"meterName": "DefaultMeter",
"views": 2,
"totalViews":2
"viewsLeft": 3,
"maxViews": 5
}
The meterName attribute will have the same value as the Meter Name specified in Composer's Pageview Meter card.
meterExpired
The meterExpired event fires when a user has reached the allowed number of views. The format of meterData object is the same as in meterActive event.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "meterExpired", function(meterData){
// The logic executed here could be differentiated
// Based on meterData.meterName value
}]);
beforeBrowserEvent
The beforeBrowserEvent event fires before each event card. If the publisher's callback returns false, the corresponding event will be suppressed. Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "beforeBrowserEvent", function (eventParams) {
// Add conditions here. In this case, we are suppressing a viewportExit event if a video is playing.
if (window.videoIsPlaying && eventParams.type === "viewportExit") {
console.log("Suppress viewportExit event");
return false;
}
return true;
}]);
The types of eventParams are: interaction, scrollDepth, timer, idle, and viewportExit.
applyCss
The applyCss is triggered when Composer executes the Apply CSS card.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "applyCss", function(eventParams) {
console.log("CSS rules applied:", eventParams.ruleList);
eventParams.ruleList.forEach(function(rule) {
console.log("Rule type:", rule.type); // value "add" or "remove"
console.log("Elements affected:", rule.elements); // the element defined in the Apply CSS card
console.log("Classes:", rule.classes); // the class defined in the Apply CSS card
});
}]);
creditRedeemed
The creditRedeemed event is triggered when a Credit is redeemed.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "creditRedeemed", function(eventParams) {
// your custom code
}]);
nonSite
The nonSite event is triggered when Composer executes the Non-site action card.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "nonSite", function(eventParams) {
// your custom code
}]);
runJs
The runJs event is triggered when Composer executes the Run JS card.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "runJs", function(eventParams) {
console.log("JavaScript snippet executed:", eventParams.snippet); // code from Run JS card
}]);
setCookie
The setCookie event is triggered when Composer executes the Set cookie card.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "setCookie", function(eventParams) {
console.log("Cookie set:", eventParams.cookieName); // cookie name without _pc_ prefix
console.log("Cookie value:", eventParams.cookieValue);
console.log("Expiration:", eventParams.expirationUnit, eventParams.expirationValue);
}]);
setResponseVariable
The setResponseVariable is triggered when Composer executes the Set response variable card. The data passed into this callback contains the JSON that was configured to be passed in this card. More information about how to set response variables is available here.
tp = window.tp || [];
tp.push(["addHandler", "setResponseVariable", function(eventParams){
// The responseVariables attribute will contain the actual object with values
var value = eventParams.responseVariables
}]);
showForm
The showForm is triggered when Composer executes the Show form card.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "showForm", function(eventParams) {
console.log("Form shown:", eventParams.formName); // name of the form
}]);
showLogin
The showLogin is triggered when Composer executes the Show login card for Mobile Experiences.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "showLogin", function(eventParams) {
// your custom code
}]);
showRecommendations
The showRecommendations callback is triggered when Composer executes the Show recommendations card. More information about this is available here.
It can be used for tracking impressions for Content recommendations.
Here is the JavaScript configuration to listen for this event:
tp.push(["addHandler", "showRecommendations", function(data){
console.log(data);
}]);
Sample response:
{
containerSelector: ".inline-container"
displayMode: "inline"
showCloseButton: false
siteId: "<Your_Site_ID>"
type: "CXENSE"
widgetId: "<Recommendation_Widget_ID>"
}
Below is a sample code that can be added to the widget's code, and would amend a UTM on to the Content recommendation clicks that can be tracked by 3rd party analytics software:
var items = data.response.items;
for (var i = 0; i < items.length; i++) {
var item = items[i];
var sep = item.click_url.indexOf('?') > -1 ? '&' : '?';
item.click_url = item.click_url + sep + 'utm_src=pianocontent';
For more information about tracking Content recommendation clicks, please review the documentation here and here.
userSegmentTrue | userSegmentFalse
The userSegmentTrue and userSegmentFalse callbacks are triggered when a visitor either matches or does not match the conditions of the user segment card.
Here's the JavaScript configuration to listen for this event:
tp.push(["addHandler", "userSegmentTrue", function(eventParams) {
console.log("User segment condition is true", eventParams);
}]);
tp.push(["addHandler", "userSegmentFalse", function(eventParams) {
console.log("User segment condition is false", eventParams);
}]);
customEvent
The customEvent callback can be used to track custom events within the My Account. When editing your My Account templates, any element with the external-event directive will trigger a custom event.
Here is the JavaScript configuration to listen for this event:
tp.push(['addHandler', 'customEvent', function(event)
{
if (event.eventName === 'cancel-sub-click') {
// do something
}
}]);