Subscriptions
Both the Zlick SDK and widget allow you to accept recurring-payments for the subscriptions you offer.
When creating an account with Zlick, you have to define the subscription plans you offer in advance. These plans are stored in the Zlick database and to activate this subscription for a certain user, you just have to refer to it by its unique name.
A typical subscription plan has the following properties:
- Friendly display name
- Unique product id
- Pricing information:
- Price
- Charging interval: ('hour', 'day', 'week', 'month', 'year')
- Currency
- Refund period: time during which the subscription can be cancelled/refunded
- Webhook callback: This is a URL in your backend system where Zlick will send notifications whenever subscription status changes or after every succussfull or failed transaction. You can use these notifications to update a user's subscription status inside your own system.
When the onboarding process is complete, you can then refer to this subscription and start activating it for users on your site through the subscription's unique name.
Subscription Flow
The following chart might help you understand the flow of subscriptions on the Zlick platform.
Webhook Callback
The webhook callback is defined during the onboarding process and is used by zlick to notify you for any updates in the transaction or subscription. Zlick will send a POST
request to your defined endpoint with content-type application/json
.
Retry Mechanism
Zlick will make 3 retries to your endpoint for any failed callback that returns a response. Retry cycle will be performed on the following response status codes (100 - 199
), (429 - 429
), (500 - 599
) and will retry twice on errors that don’t return a response (ENOTFOUND
, ETIMEDOUT
, etc)
Webhook Body
Webhook's body is different depending on whether it is a subscription callback or a transaction callback and whether or not it was a success or a failure.
Transaction
Success
{
"eventId": "642ac976-352a-45ed-b263-8da194a53db7",
"event": "transaction.completed",
"apiVersion": "2020-10-22",
"data": {
"productId": "zlick-product-123",
"amount": 299, # price of product in change
"clientTransactionId": "tms-1234", # client transaction id (not applicable for Apollo)
"zlickTransactionId": "b84bf4c3-091e-4621-bfd0-54c0a48cb405", # unique transaction ID from Zlick
"subscriptionId": "a74bf4c3-094e-5621-bfd0-54c0a48cb307", # unique subscription ID from Zlick
"subscriptionProductName" : "PRODUCT_NAME",
"isSubscription": true,
"clientUserId": "u123" # user id specified by client SDK
},
"livemode": false,
"timestamp": "2020-08-17T11:42:43.007Z"
}
Error
{
"eventId": "642ac976-352a-45ed-b263-8da194a53db7",
"event": "transaction.failed",
"apiVersion": "2020-10-22",
"data": {
"desc": "", # error description (if available)
"zlickErrorCode": "ZLICK05", # one of Zlick's internal Error codes. See below.
"productId": "zlick-product-123",
"amount": 299,
"clientTransactionId": "tms-1234",
"zlickTransactionId": "b84bf4c3-091e-4621-bfd0-54c0a48cb405",
"subscriptionId": "a74bf4c3-094e-5621-bfd0-54c0a48cb307", # unique subscription ID from Zlick
"subscriptionProductName" : "PRODUCT_NAME",
"isSubscription": true,
"clientUserId": "u123"
},
"livemode": false,
"timestamp": "2020-08-17T11:42:43.007Z"
}
Subscription
Status
The following webhook is sent whenever subscription status is changed. Use data.state
to set subscription status in your own system.
Success
{
"eventId": "642ac976-352a-45ed-b263-8da194a53db7", # Always unique for each webhook request
"event": "subscription.status",
"apiVersion": "2020-10-22",
"data": {
"clientUserId": "accc61ba",
"expiresAt": "2020-02-17T11:26:49.631Z",
"productName": "zlick-plan-1", # subscription name
# state values can be "canceled", "ended", "inactive", "active"
# ended - limited subscription has reached its end
# inactive - subscription ended manually
# canceled - no further payments are generated. generally due to payment failure
"state": "active",
"subscriptionId": "6f87fe56-50b0-4e64-93c3-86f930fc3bd5"
},
"livemode": false,
# when this event was generated
"timestamp": "2020-08-17T11:42:43.007Z"
}
Webhook signature
Verify the events that Zlick sends to your webhook endpoints.
Zlick signs the webhook events it sends to your endpoints by including a signature in each event’s signature header. This allows you to verify that the events were sent by Zlick, not by a third party.
You can verify the signature with the api client secret key provided by Zlick during the onboarding process.
Preventing replay attacks
A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To mitigate such attacks, Zlick includes a timestamp in the signature header. Because this timestamp is part of the signed payload, it is also verified by the signature, so an attacker cannot change the timestamp without invalidating the signature. If the signature is valid but the timestamp is too old, you can have your application reject the payload.
You can have a tolerance of few minutes between the timestamp and the current time. You can use this tolerance to either accept or reject the payload. Zlick uses Coordinated Universal Time (UTC) as a standard for timestamp value in webhooks.
Verifying signatures
The signature header included in each signed event contains a timestamp and one signature. The timestamp is prefixed by t=, and the signature is prefixed by v=.
signature:
t=1597676339727,
v=3af6267fbd9693391b96d3d1fb5266733991ce2bc09684f68c0af3bd89614b77
Note that newlines have been added for clarity, but a real signature
header is on a single line.
Zlick generates signatures using a hash-based message authentication code (HMAC) with SHA-256.
You can follow these steps to verify webhook event signature.
Step 1: Extract the timestamp and signature from the header
Split the header, using the ,
character as the separator, to get a list of elements. Then split each element, using the =
character as the separator, to get a prefix and value pair.
The value for the prefix t
corresponds to the timestamp in milliseconds, and v
corresponds to the signature.
Step 2: Prepare the signed_payload string
The signed_payload
string is created by concatenating:
- The timestamp (as a string)
- The character
.
- The actual JSON payload (i.e., the request body)
Step 3: Determine the expected signature
Compute an HMAC with the SHA256 hash function. Use the api client secret as the key, and use the signed_payload
string as the input string/message.
Step 4: Compare the signature
Compare the signature in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.
To protect against timing attacks, use a constant-time string comparison to compare the expected signature to the received signature.
Node JS Code Example
const crypto = require('crypto')
const moment = require('moment')
const validateSignature = ({ body, signature }) => {
const payload = JSON.stringify(body)
let pairs = signature.split(',')
pairs = pairs.map(v=>{
const tmp = v.split("=");
return {[tmp[0]]:tmp[1]}
})
const request_timestamp = pairs[0].t
const request_hmac = pairs[1].v
let signed_payload = `${pairs[0].t}.${payload}`
signed_payload = crypto.createHmac('sha256', api_secret)
.update(signed_payload)
.digest('hex')
if (signed_payload === request_hmac){
// hmac matched
const tolerance = moment.utc().valueOf() - request_timestamp
// tolerance in milliseconds
if (tolerance > 10*60*1000){ // tolerance of 10 minutes
return false
}
return true
} else {
// miss match
return false
}
}
// Client secret by Zlick
const api_secret = 'a9f8880a41cca0524a0815df'
// Request
const request = {
headers: {
signature: 't=1597676339727,v=3af6267fbd9693391b96d3d1fb5266733991ce2bc09684f68c0af3bd89614b77'
},
data: {
eventId: "642ac986-382a-45ed-b363-8da124a53db7",
event: "subscription.status",
apiVersion: "2020-10-22",
data: {
clientUserId: "accc61ba",
expiresAt: "2020-02-17T11:26:49.631Z",
productName: "zlick-plan-1",
state: "active",
subscriptionId: "8f87fe55-50b0-4e64-93c3-86f930fc3bd5"
},
livemode: false,
timestamp: "2020-08-17T12:39:46.553Z"
}
}
if (validateSignature({
body: request["data"],
signature: request["headers"]["signature"]
})) {
// Use the response for your needs
const response = request["data"]
console.log("accepted")
// ...
} else {
// Validation failed
// Reject Payload
// ...
console.log("rejected")
}
Zlick Error Codes
- ZLICK01 - User has insufficient credit for transaction
- ZLICK02 - Phone is either moved to another telco or closed
- ZLICK03 - Exceeds allowed transaction limit
- ZLICK04 - Transaction is not permitted, premium services not allowed
- ZLICK05 - Unexpected error has caused an exception.
- ZLICK06 - Transaction failed. Technical error. ( Usually caused by an unexpected response from the carrier )
-
ZLICK07 - Zlick was unable to get response from service provider
-
ZLICK08 - Zlick was unable to get response from the end user
- ZLICK09 - Carrier is not supported or enabled
- ZLICK10 - Unable to detect carrier for the phone number
- ZLICK11 - Country is not supported
- ZLICK12 - Access is blocked
- ZLICK13 - Error processing request. The requested resource is forbidden.
- ZLICK14 - Limit exceeded. Too many requests.
- ZLICK15 - User already has an active subscription.