This page applies to OAuth apps published in Zid App Market. Shipping apps receive a different set of events β installation, order and product events, but no subscription events. Private apps are not sold through the market and have no subscriptions.
GET /v1/market/app/subscription with a merchant's token and get their current subscription back. You decide when to ask. Full details are in Reference: the Subscription Details endpoint below.| Events | Subscription Details endpoint | |
|---|---|---|
| Tells you | That something changed, as it happens | What is true right now |
| Setup | Subscribe to each event in the Partner Dashboard | Request the Subscription.read scope |
| Covers | All your merchants, through one endpoint | One merchant per call |
| Charge details | Not included in the usage-based payloads | Full charge records β id, amount, status, overdue date, and pending and settled totals |
| Depends on | Your endpoint being reachable when Zid calls | You remembering to call it |
| After a merchant uninstalls | You still receive the uninstall event | Stops working β see below |
X-Manager-Token and revokes your OAuth access and refresh tokens for that store. Your next call fails β and a failed call looks the same as a network problem or an expired token. The app.market.application.uninstall event is the only reliable way to learn that it happened, and it is the signal that tells you to stop serving that merchant.uninstall and expired β those are how you learn to stop serving a merchant.| Event | When it fires |
|---|---|
app.market.application.authorized | The merchant grants your app its permissions, before installing |
app.market.application.install | The merchant installs your app |
app.market.application.uninstall | The merchant removes your app |
app.market.application.rated | The merchant leaves a rating or review |
app.market.private.plan.request | The merchant requests a private plan |
| Event | When it fires |
|---|---|
app.market.subscription.active | A subscription becomes active after payment |
app.market.subscription.warning | See the timing table below |
app.market.subscription.suspended | 1 day after a recurring subscription ends |
app.market.subscription.expired | See the timing table below |
app.market.subscription.renew | After a renewal or extension completes. Also fires on free-plan renewal, when a paused subscription resumes, and when a usage-based charge is paid |
app.market.subscription.upgrade | The merchant moves to a different plan |
app.market.subscription.refunded | A payment is refunded to the merchant |
app.market.subscription.usage_based.payment_success | A usage-based charge is paid |
app.market.subscription.usage_based.payment_failure | A usage-based charge payment fails |
renewdoes not warn you before a renewal. It always fires after the fact. No event is sent before a subscription renews or expires β usewarningfor advance notice.
warning and expired are used for both recurring and usage-based plans, but they mean different things and arrive at different times.| Event | Recurring plans | Usage-based plans |
|---|---|---|
warning | 3 days before the subscription ends | 30 days after a charge is left unpaid |
suspended | 1 day after the subscription ends | Not used |
expired | 5 days after the subscription ends | 30 days after the charge became overdue |
plan_type in the payload if you need to tell them apart.active, warning, suspended, expired, renew, upgrade and refunded, share one payload shape.{
"app_id": 1292,
"store_id": 507530,
"store_uuid": "d78468cd-4b44-4fee-a00d-76561f35787c",
"store_url": "https://wyatt.com.sa/",
"plan_name": "Ψ§ΩΩΩ
Ω",
"plan_id": 904,
"plan_type": "Paid",
"status": "active",
"start_date": "2025-02-21T08:05:29.000000Z",
"end_date": "2025-03-22T08:05:29.000000Z",
"amount_paid": 79.01,
"old_plan_name": null,
"merchant_email": "info@ehmp.com.sa",
"merchant_phone_no": "966554349999",
"event_name": "app.market.subscription.active",
"payment_date": "2025-02-21T08:05:29.000000Z"
}| Field | Notes |
|---|---|
app_id | Your app's ID |
store_id | Internal numeric store ID |
store_uuid | Use this to identify the store, not store_id |
store_url | The merchant's storefront URL |
plan_name | The plan the merchant is on. null if they have no subscription |
plan_id | The plan's ID |
plan_type | One of Paid, Free, Free Trial, Usage Based, Free (Without Plan). See Identifying free trials |
status | The subscription's status. Not a reliable way to tell events apart β use event_name. See below |
start_date | When the subscription started |
end_date | When it ends. Always null for usage-based plans, which have no end date |
amount_paid | Amount paid, including VAT. 0 on many events β see below |
old_plan_name | The previous plan's name. Only set on active, renew and upgrade; null otherwise |
merchant_email | The merchant's email |
merchant_phone_no | The merchant's phone number |
event_name | The event. Use this to identify what happened |
payment_date | For a usage-based charge, when the charge was paid. Otherwise the subscription start date |
| Events | amount_paid | status |
|---|---|---|
active, renew, upgrade, refunded | The actual amount paid, including VAT | The subscription's status |
warning, suspended, expired | Always 0 | warning, suspend, expire |
authorized, install, uninstall, rated, private.plan.request | Always 0 | The merchant's current subscription status, not the event name |
amount_paid from an install or authorized event β it is always zero. Read it from active, renew or upgrade.status fieldstatus reports the subscription's status at the moment the event was built. Its possible values are:inactive, active, warning, suspend, frozen, expire, renew, upgrade, cancel, refundsuspend not "suspended", expire not "expired", cancel not "cancelled", refund not "refunded".status falls back to the last part of the event name (for example authorized for app.market.application.authorized). Because of this, status is not a dependable event discriminator. Always branch on event_name.plan_type:{
"app_id": 6528,
"store_id": 3104452,
"store_uuid": "b2052c72-ddaa-411f-a428-748dcd885a6e",
"store_url": "https://xle6e8.zid.store/",
"plan_name": "Test Plan With Trial",
"plan_id": 3329,
"plan_type": "Free Trial",
"status": "active",
"start_date": "2026-06-14T08:05:40.000000Z",
"end_date": "2026-07-04T08:05:40.000000Z",
"amount_paid": 0,
"old_plan_name": null,
"merchant_email": "bv3e7xxnx5@zam-partner.email",
"merchant_phone_no": "+201101050117",
"event_name": "app.market.subscription.active",
"payment_date": "2026-06-14T08:05:40.000000Z"
}plan_type is "Free Trial" here, so this subscription includes a trial.plan_typetells you the subscription has a trial, not that the trial is still running.A subscription keeps the "Free Trial"value for as long as that subscription record exists, including after the trial converts to paid. The payload does not include the trial end date.The Subscription Details endpoint does not help here either β it returns no plan_typeand no trial dates. To know whether a trial is still running, work it out yourself: you set the trial length on the plan, so add it tostart_date.
usage_based.payment_success and usage_based.payment_failure use a different shape. None of the fields above apply.{
"action": "usage_based_subscription_payment_success",
"envelope": {
"store_id": 12345,
"app": {
"app_id": 67,
"app_name": "My App"
},
"subscription": {
"plan": "Professional",
"plan_price": 99.00
}
}
}action:{
"action": "usage_based_subscription_payment_failure",
"envelope": {
"store_id": 12345,
"app": {
"app_id": 67,
"app_name": "My App"
},
"subscription": {
"plan": "Professional",
"plan_price": 99.00
}
}
}| Field | Type | Description |
|---|---|---|
action | string | Either usage_based_subscription_payment_success or usage_based_subscription_payment_failure |
envelope.store_id | integer | The store where the charge was attempted |
envelope.app.app_id | integer | Your app's ID |
envelope.app.app_name | string | Your app's name |
envelope.subscription.plan | string | The plan's name |
envelope.subscription.plan_price | number | The plan's price |
plan_price is the plan's own price, not the amount the merchant just paid.store_uuid β only the numeric store_id.app.market.subscription.usage_based.payment_success β tells you a payment succeededapp.market.subscription.renew β carries the amount in amount_paid and the date in payment_daterenew event, or call the Subscription Details endpoint.usage_based.payment_failure is sent, and it does not tell you which charge failed or for how much. Call Subscription Details to find out.https://api.zid.sa/v1/market/app/subscriptionSubscription.read| Header | Value |
|---|---|
Authorization | Bearer {your partner token} |
X-Manager-Token | The merchant's token from OAuth |
Accept | application/json |
Accept-Language | ar or en. Defaults to en. Controls the language of plan_name |
X-Manager-Token. You do not send an app id.{
"status": "object",
"subscription": {
"app_id": 2376,
"store_id": 26520,
"store_url": "https://example.zid.store/",
"plan_name": "Pay as you go",
"subscription_status": "active",
"start_date": "2025-02-04T15:55:07.000000Z",
"end_date": null,
"merchant_phone_no": "+9665XXXXXXX",
"merchant_email": "merchant@example.com",
"is_usage_based": 1,
"pending_charges": 345.00,
"settled_charges": 1200.00,
"charges": [
{
"id": "9eaac8b0-3530-4bdb-b2fb-34b4efe22179",
"subscription_id": 333,
"amount": 345.00,
"amount_details": {
"amount": 34500,
"currency": "SAR",
"formatted": "Ψ±.Ψ³β 345.00"
},
"status": "pending",
"overdue_at": null,
"created_at": "2025-04-13T16:58:06.000000Z"
}
]
},
"message": {
"type": "object",
"code": null,
"name": null,
"description": "Store subscription details of the provided token"
}
}| Field | Notes |
|---|---|
app_id | Your app's ID |
store_id | The merchant's store ID |
store_url | The merchant's storefront URL |
plan_name | The plan's name, in the language from Accept-Language |
subscription_status | One of inactive, active, warning, suspend, frozen, expire, renew, upgrade, cancel, refund |
start_date | When the subscription started |
end_date | When it ends. Always null for usage-based plans |
merchant_phone_no | The merchant's phone number |
merchant_email | The merchant's email |
is_usage_based | 1 for a usage-based plan, 0 otherwise |
is_usage_based is 1. On a recurring or free plan they are not in the response at all.| Field | Notes |
|---|---|
pending_charges | The unpaid charge's amount, in SAR. 0.0 if there is none |
settled_charges | Total settled so far, in SAR |
charges | The charge records, newest first |
charges:| Field | Notes |
|---|---|
id | The charge's ID. This is the id the events do not give you |
subscription_id | The subscription the charge belongs to |
amount | The amount in SAR β for example 345.00 |
amount_details.amount | The same amount in halalas β for example 34500 |
amount_details.currency | Currency code |
amount_details.formatted | The amount formatted for display |
status | One of pending, paid, overdue, defaulted |
overdue_at | When the charge became overdue, or null |
created_at | When you created the charge |
Two fields named amount, two different units.amountis in SAR;amount_details.amountis in halalas. Don't mix them up.amount_detailsis empty when the amount is zero.
subscription_status before treating it as current.subscription comes back empty if the merchant has not activated a plan. You get "subscription": [] β an empty value, not an object with empty fields. Installing your app is not enough on its own: a merchant activates a free plan, starts a trial, or pays for a plan, and only then does a subscription exist. An app with no plans at all will always return empty here.plan_id, no plan_type and no store_uuid. If you key merchants on store_uuid from events, you cannot match that against this response β use store_id as well, or store both.| Status | Meaning |
|---|---|
| 401 | Credentials missing, invalid or expired |
| 500 | Server error |
event_name, not status. status is ambiguous across events and uses shortened values.uninstall and expired. These are how you learn to stop serving a merchant. Without them your app keeps working for merchants who no longer have access.The webhook health endpoints documented under Webhooks β health summary, broken webhooks, recover broken webhooks β apply to store data webhooks such as orders, products and customers. They do not cover the app events on this page.
| App events (this page) | Store data webhooks | |
|---|---|---|
| What they tell you | Your app was installed, a subscription changed | A merchant's order, product or customer changed |
| Event names | app.market.* | order.*, product.*, customer.*, category.* |
| How you subscribe | Once per app, in the Partner Dashboard | Per store, with the Create Webhook API |
| Scope needed | None β it's dashboard configuration | third_webhook_write |