Receiving and understanding messages

Request

Sample header

Accept: application/json
X-PURCHASELY-SIGNATURE: ea909...ba5a6,
X-PURCHASELY-TIMESTAMP: 1580...929

To ensure that events are indeed coming from Purchasely Cloud Platform, you can authentify event using informations contained in the HEADER of the HTTP request :

  • X-PURCHASELY-SIGNATURE : message signature

  • X-PURCHASELY-TIMESTAMP : request timestamp to avoid replay attacks

This verification is optionnal.

The timestamp correspond to the query time and (UTC) and it can be ignored if the query is too old (more than 15 minutes ?)

The signature relies on a shared secret that you can find in your Purchasely Console (client_webhook_shared_secret) Purchasely Console > Applications > [YOUR APP] > Application settings

Sample code for signature verification (NodeJS) :

JavaScript
Ruby
JavaScript
const crypto = require("crypto");
// Request headers
// ---------------
const xPurchaselyTimestamp = "1580909929";
const xPurchaselySignature = "ea909b88098b63ef93711cd14542403e5efe1a23c07d94a764bd4db55abba5a6";
// Signature verification
// ----------------------
const webhookSharedSecret = "foobar";
const dataToSign = webhookSharedSecret + xPurchaselyTimestamp;
const computedSignature = crypto
.createHmac("sha256", webhookSharedSecret)
.update(dataToSign)
.digest("hex");
if (computedSignature === xPurchaselySignature) {
// request authenticated
}
Ruby
require 'openssl'
# Request headers
# ---------------
x_purchasely_timestamp = '1580909929'
x_purchasely_signature = 'ea909b88098b63ef93711cd14542403e5efe1a23c07d94a764bd4db55abba5a6'
# Signature verification
# ----------------------
webhook_shared_secret = 'foobar'
data_to_sign = webhook_shared_secret + x_purchasely_timestamp
computed_signature = OpenSSL::HMAC.hexdigest('sha256', webhook_shared_secret, data_to_sign)
if (computed_signature == x_purchasely_signature) {
# request authenticated
}

Body

Sample body

{
"name": "PURCHASE_VALIDATED",
"user": {
"vendor_id": "5e2dd8f8a372b06a32e9e73c" // or "anonymous_id" if not logged_in
},
"properties": {
"purchase_id": "subs_iHyp7trFLoIOccE6x9gaatMfb7uCi3V",
"product": {
"vendor_id": "PURCHASELY_PLUS",
"plan": {
"type": "RENEWING_SUBSCRIPTION",
"vendor_id": "PURCHASELY_PLUS_MONTHLY"
}
},
"store": "APPLE_APP_STORE", // can be GOOGLE_PLAY_STORE | HUAWEI_APP_GALLERY
"app": {
"platform": "IOS", // can be ANDROID
"package_id": "io.purchasely.app"
},
"expires_at": "2020-09-25T14:30:37.000Z",
"purchased_at": "2020-08-25T14:30:37.000Z"
},
"received_at": "2020-08-25T14:31:04.469Z"
}

Model

Event

  • name : Event Name Possible values : PURCHASE_VALIDATED | SUBSCRIPTION_RENEWED | SUBSCRIPTION_EXPIRED … (full list) See Events repository to understand the exact meaning for each Event, when and why use them.

  • received_at : Timestamp of the date at which the Event has been received Date format : YYYY-MM-DDThh:mm:ss.00Z (ISO_8601)

Properties

  • purchase_id : purchasely id of the purchase

  • product.vendor_id : Product unique identifier. This ID corresponds to your own identifier and is set in the Purchasely Console (see Configuring Products & Plans). This allows you to manage your catalogue of products using directly your own identifiers

  • plan.vendor_id : Plan unique identifier. This ID corresponds to your own identifier and is set in the Purchasely Console (see Configuring Products & Plans).

    This allows you to manage your catalogue of plans using directly your own identifiers

  • plan.type : Type of Plan. Possible values : RENEWING_SUBSCRIPTION | NON_RENEWING_SUBSCRIPTION | CONSUMABLE | NON_CONSUMABLE Only RENEWING_SUBSCRIPTIONS are handled for now.

  • store : The Store on which the purchase was made. Possible values : APPLE_APP_STORE | GOOGLE_PLAY_STORE | HUAWEI_APP_GALLERY

  • app.platform : Platform OS on which the purchase was made. Possible values : IOS | ANDROID

  • app.package_id : Application Bundle ID in which the purchase was made

  • purchased_at : purchase date

    Date format : YYYY-MM-DDThh:mm:ss.00Z (ISO_8601)

  • expires_at : expiration date (only set when plan.type is RENEWING_SUBSCRIPTION or NON_RENEWING_SUBSCRIPTION)

    Date format : YYYY-MM-DDThh:mm:ss.00Z (ISO_8601)

Never use the expires_atto invalidate a subscription (and always use the webhook sent to you for this sole purpose). This date is only here to help your marketing team take actions (or if you want to display the next renewal date in your app).

If you ever needed a fail safe to unsubscribe users in case an issue occurs with Apple/Google/Huawei/Purchasely/your servers, you should let at least a 24h-margin with the given expires_at.

User properties

  • vendor_id : User unique identifier which corresponds to your owner identifier. This information is passed to Purchasely through the SDK (method setUserId). This allows you to manage directly your users with your own identifier.

  • anonymous_id : Purchasely anonymous user id. It is generated by the Purchasely Cloud Platform. This property shall only be considered when no vendor_id has been set. It allows to manage anonymous purchases.

Response

When called by Purchasely Cloud Platform, client backend should respond with a HTTP code :

  • HTTP 200 ⇒ the Event has been well received and processed (eg: the subscription has been activated)

  • Other than HTTP 200 or no response (timeout) ⇒ an error has occured and the Event could not be processed :

    • The user is warned through the SDK that something did not work

    • Purchasely Cloud Platform will retry several times to send the Event (max 25 times) in the following hours (except for HTTP 404).

This response from the client backend to the Purchasely Console is mandatory particularly for purchase events (e.g. new subscriptions) coming from the SDK, to ensure that the client backend has granted the user with the entitlements corresponding to the new purchase, and unlocked the access to the premium contents or features.

This response from the client backend is forwarded to the mobile SDK and an error message is displayed to the user, if it is different from HTTP 200.