Myket In-App Purchase Guide for Web Application
For an example implementation of Myket in-app purchases in a web application, see the sample project on GitHub.
This page describes Myket's direct-purchase flow for web apps, PWAs, and similar platforms that cannot use the Myket IAB library. No SDK needs to be installed: the web app makes a few HTTP requests through its server and redirects the user's browser to complete the purchase.
Keep the Myket access token on your server. Start, verify, and consume requests must be server-to-server. The purchase callback is a POST from the user's browser, not a server-to-server webhook; grant the product only after verifying the purchase with Myket's verification API.
Direct-purchase flow
- Your server calls the purchase-start service and receives a payment URL.
- Redirect the user's browser to that URL. The user signs in to Myket, selects a payment method, and completes the transaction.
- After the flow ends, the user's browser POSTs a form to your
callbackUrl. - Your server inspects the callback and verifies the purchase with Myket's API.
- After delivering a consumable product, your server consumes it through the API.
1. Get the purchase-start URL
Call this service only from your app's server. Calling it from a client can expose the developer access token. The Python examples use the requests library; install it with python -m pip install requests if needed.
- cURL
- Python
curl -X POST \
--location 'https://developer.myket.ir/api/partners/applications/{PACKAGE_NAME}/purchases/iap/start' \
--header 'X-Access-Token: {ACCESS_TOKEN}' \
--header 'Content-Type: application/json' \
--data '{
"skuId": "{SKU_ID}",
"callbackUrl": "{CALLBACK_URL}",
"developerPayload": "{DEVELOPER_PAYLOAD}"
}'
import requests
package_name = "{PACKAGE_NAME}"
sku_id = "{SKU_ID}"
access_token = "{ACCESS_TOKEN}"
callback_url = "{CALLBACK_URL}"
developer_payload = "{DEVELOPER_PAYLOAD}"
url = f"https://developer.myket.ir/api/partners/applications/{package_name}/purchases/iap/start"
headers = {
"X-Access-Token": access_token,
"Content-Type": "application/json",
}
payload = {
"skuId": sku_id,
"callbackUrl": callback_url,
"developerPayload": developer_payload,
}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code, response.json())
Get X-Access-Token from the in-app products section for the app in the Myket Developer Panel.
| Parameter | Location | Description |
|---|---|---|
X-Access-Token | Header | The app's verification token from the Developer Panel. Keep it secret. |
PACKAGE_NAME | URL path | The package name registered with Myket. |
skuId | JSON body | The in-app product ID. |
callbackUrl | JSON body | A public URL where the user's browser can POST the callback form after the purchase. |
developerPayload | JSON body | A non-empty application-defined value for correlating the purchase with your order or user; maximum 800 characters. |
The developerPayload string must not exceed 800 characters.
A successful 200 response contains the purchase-start URL:
{
"url": "https://iap.myket.ir/purchases/{PURCHASE_ID}/start"
}
An invalid URL may return 400 with messageCode set to InvalidUrl. Other errors can occur if the package name is invalid or not registered to the developer, the SKU is not found, callbackUrl is empty or invalid, or developerPayload is empty or longer than 800 characters.
2. Redirect the user's browser
Redirect the user's browser (not your server) to the url from the previous response. This Myket page is where the user signs in, chooses a payment method, and completes the transaction. Use the URL returned by the service; do not guess or reconstruct it.
3. Receive and inspect the callback
After the purchase flow ends, Myket displays a page containing an HTML form. The user's browser automatically POSTs it to callbackUrl with Content-Type: application/x-www-form-urlencoded. This request comes from the user's browser, not from Myket's server.
| Field | Description |
|---|---|
status | Successful for a successful purchase, Failed for a failed purchase, or AlreadyPaid if the user has already completed the purchase. |
invoice | A JSON string containing transaction details; empty for a failed purchase. |
signature | A digital signature over the invoice string; empty for a failed purchase. |
Example invoice structure:
{
"orderId": "60004211",
"packageName": "com.myket.sample",
"productId": "GEM2",
"purchaseTime": 1785081672000,
"developerPayload": "order=45157",
"purchaseToken": "60004211",
"purchaseState": 0,
"autoRenew": false
}
| Invoice field | Description |
|---|---|
orderId | Order ID. |
packageName | App package name. |
productId | In-app product ID; the same value as skuId used to start the purchase. |
purchaseTime | Transaction time as a Unix timestamp in milliseconds. |
developerPayload | The value sent when starting the purchase. |
purchaseToken | Token used with the verify and consume APIs. |
purchaseState | 0 for a successful purchase; 1 for an unsuccessful purchase. |
autoRenew | Reserved for future use; do not rely on it currently. |
Verify the digital signature
The signature is calculated over the UTF-8 bytes of the raw invoice string after URL decoding, not over reserialized JSON. Do not parse and serialize the value again: changing key order, whitespace, or formatting can cause signature verification to fail. Verify the decoded string without modification as raw bytes.
Get the app's public key from the in-app products page in the Myket Developer Panel (the Public key button). It is provided as Base64-encoded DER in the X.509 SubjectPublicKeyInfo format. The signature algorithm is RSA with a 1,024-bit key, SHA-1, and PKCS#1 v1.5 padding (not PSS). The Python example uses the cryptography library; install it with python -m pip install cryptography if needed.
Signature verification examples:
- Python
- Java
- .NET
import base64
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
def verify_signature(raw_invoice, signature_base64, public_key_base64):
public_key = serialization.load_der_public_key(
base64.b64decode(public_key_base64)
)
signature = base64.b64decode(signature_base64)
try:
public_key.verify(
signature,
raw_invoice.encode("utf-8"),
padding.PKCS1v15(),
hashes.SHA1(),
)
return True
except InvalidSignature:
return False
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public final class CallbackVerifier {
public static boolean verifySignature(
String rawInvoice,
String signatureBase64,
String publicKeyBase64) throws Exception {
byte[] publicKeyDer = Base64.getDecoder().decode(publicKeyBase64);
PublicKey publicKey = KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(publicKeyDer));
Signature verifier = Signature.getInstance("SHA1withRSA");
verifier.initVerify(publicKey);
verifier.update(rawInvoice.getBytes(StandardCharsets.UTF_8));
return verifier.verify(Base64.getDecoder().decode(signatureBase64));
}
}
using System;
using System.Security.Cryptography;
using System.Text;
public static class CallbackVerifier
{
public static bool VerifySignature(
string rawInvoice,
string signatureBase64,
string publicKeyBase64)
{
using var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(
Convert.FromBase64String(publicKeyBase64), out _);
var invoiceBytes = Encoding.UTF8.GetBytes(rawInvoice);
var signature = Convert.FromBase64String(signatureBase64);
return rsa.VerifyData(
invoiceBytes,
signature,
HashAlgorithmName.SHA1,
RSASignaturePadding.Pkcs1);
}
}
A valid signature only indicates that the invoice data has not been altered; it is not sufficient to grant a product. Since the form comes from the user's browser, neither receiving it nor validating its signature replaces server-to-server purchase verification.
4. Verify the purchase with Myket's API
Your server must verify the purchaseToken from the invoice with Myket's purchase-verification API. This API is server-to-server only; never put the access token in the client app.
- cURL
- Python
curl -X POST \
--location 'https://developer.myket.ir/api/partners/applications/{PACKAGE_NAME}/purchases/products/{SKU_ID}/verify' \
--header 'X-Access-Token: {ACCESS_TOKEN}' \
--header 'Content-Type: application/json' \
--data '{
"tokenId": "{PURCHASE_TOKEN}"
}'
import requests
package_name = "{PACKAGE_NAME}"
sku_id = "{SKU_ID}"
access_token = "{ACCESS_TOKEN}"
purchase_token = "{PURCHASE_TOKEN}"
url = f"https://developer.myket.ir/api/partners/applications/{package_name}/purchases/products/{sku_id}/verify"
headers = {
"X-Access-Token": access_token,
"Content-Type": "application/json",
}
payload = {"tokenId": purchase_token}
response = requests.post(url, headers=headers, json=payload)
print(response.status_code, response.json())
| Parameter | Location | Description |
|---|---|---|
X-Access-Token | Header | The app's verification token from the Developer Panel. |
PACKAGE_NAME | URL path | The app package name. |
SKU_ID | URL path | The in-app product ID. |
tokenId | JSON body | The purchaseToken received in the callback. |
This API was already used to verify SDK purchases; it also verifies the purchaseToken from a direct purchase. A successful response contains these fields:
| Field | Description |
|---|---|
purchaseState | 0 for a successful purchase; 1 for an unsuccessful purchase. |
purchaseTime | Transaction time as a Unix timestamp. |
kind | Purchase type, for example androidpublisher#productPurchase. |
developerPayload | The value sent when starting the purchase. |
consumptionState | 0 for the default/non-consumable state and 1 for a consumable product that has been consumed. |
Example successful response:
{
"kind": "androidpublisher#productPurchase",
"purchaseTime": 1492839267000,
"developerPayload": "",
"purchaseState": 0,
"consumptionState": 1
}
A failed request can return 500 InternalError. Treat a purchase as valid and deliver the product or credit only when purchaseState is 0. Correlate the result with your own order to prevent duplicate delivery.
5. Consume a purchased product
After delivering a consumable product, consume it with the API below. This API must also be called from your server. Do not consume non-consumable products (for example, an ad-removal purchase).
- cURL
- Python
curl -X PUT \
--location 'https://developer.myket.ir/api/partners/applications/{PACKAGE_NAME}/purchases/products/{SKU_ID}/tokens/{PURCHASE_TOKEN}/consume' \
--header 'X-Access-Token: {ACCESS_TOKEN}' \
--header 'Content-Type: application/json'
import requests
package_name = "{PACKAGE_NAME}"
sku_id = "{SKU_ID}"
purchase_token = "{PURCHASE_TOKEN}"
access_token = "{ACCESS_TOKEN}"
url = (
f"https://developer.myket.ir/api/partners/applications/{package_name}"
f"/purchases/products/{sku_id}/tokens/{purchase_token}/consume"
)
headers = {
"X-Access-Token": access_token,
"Content-Type": "application/json",
}
response = requests.put(url, headers=headers)
print(response.status_code, response.json())
A successful response has status 200:
{
"code": 200,
"messageCode": "Successful",
"translatedMessage": "Operation completed successfully"
}
| HTTP status | messageCode | Meaning |
|---|---|---|
400 | SkuAlreadyConsumed | The product has already been consumed. |
404 | PurchasedSkuNotFound | The purchase or token was not found. |
404 | SkuIdNotFound | The in-app product was not found. |
500 | InternalError | Internal server error. |