Skip to main content
segmnt bills in SAR, per month. This page shows how to list the plans, how to send a customer to the checkout (with an optional discount code), and how a confirmed payment changes the plan. This guide is written in simple English.

Before you start

  • API in TEST: https://test-api.segmnt.cloud
  • Every billing call on this page uses your session cookie (the dashboard session). An API key cannot call them.
  • Prices are in SAR per month, before VAT.
  • The plans on sale are Starter, Pro and Agency. The free plan is the default; you do not buy it.

1. The plans on sale

GET /v1/billing/plans returns the plans a customer can buy.
The same prices, in SAR: Notes:
  • price.amount is in halalas (1 SAR = 100 halalas). 5900 halalas is 59 SAR. This is the same unit the API uses everywhere.
  • price.currency is SAR and price.interval is month.
  • price is before VAT. See VAT below.
  • limits are the plan limits (stored hours, new uploads, watched minutes, domains, projects, quality and the logo). See Usage and plan limits for what they mean.
  • The prices and limits come from the plan catalog. They are the same numbers you see on the site.
  • This call needs the session cookie. Without it you get 401 unauthorized.

2. Start a checkout

POST /v1/billing/checkout returns the payment provider’s hosted page. Send the customer there. The customer pays on Moyasar with mada, Apple Pay, STC Pay or a card.
Answer (201):
Notes:
  • The plan does not change here. It changes only after the provider confirms the payment (section 3).
  • checkoutUrl is the provider’s hosted page. Redirect the user to it.
  • expiresAt is null when the provider sets no deadline. Moyasar invoices do not expire by default, so do not invent one.
  • planId must be one of the plans on sale. An unknown or free plan gets 400 invalid_request.
  • discountCode is optional. Send an issued code and it is applied before the provider is called. See Discount codes below.
  • If online payment is not configured, checkout gets 503 billing_unavailable. Use a manual invoice.
  • If the provider does not answer, checkout gets 502 billing_provider_error. Try again.

Discount codes

POST /v1/billing/checkout takes an optional discountCode. A code is applied when it has been issued to you; creating a code is an internal segmnt action, so this page does not show how to create one.
  • A founding code gives 30% off and a referral code gives 10% off. The total discount is capped at 40%, so no code can take off more than that.
  • Codes are case-insensitive and trimmed, so founding-30 and FOUNDING-30 are the same code.
  • The code is checked before the payment provider is called. An unknown, expired or used-up code answers 400 invalid_request with a clear message, so the customer is never charged the full price by accident.
  • The discount is applied to the plan price before VAT: VAT (15%) is charged on the discounted net, not on the full price.
  • The 15% VAT invoice shows the plan line and, when a code was applied, a second negative discount line. The lines still add up to the invoice total.
  • The code is counted only when a payment is confirmed. A provider retry does not count the same code twice.
  • A confirmed payment stores the code on the subscription (discount_code).
  • The discount is kept on every renewal for the promised number of months. A founding code or a referral for the referrer lasts 12 months; a referral for the new customer lasts 3 months. The first discounted payment locks the promise on the subscription, and each later hosted payment re-applies it. Send no discountCode on a renewal: the promise stored on the subscription is applied automatically. Once the window is used up, the renewal is at the full price again.
  • The code’s used counter counts a customer once, on the first discounted payment, not once per renewal. So max_uses = 10 means 10 customers, even though each of them may pay 12 discounted months.
  • A locked promise is honored even when the code later expires or reaches its cap: the customer was promised the discount. A new customer using that same expired or used-up code is still refused with 400 invalid_request.
  • The 40% cap applies on every payment, including a renewal.

3. The payment that changes the plan

When the customer pays, Moyasar calls POST /v1/billing/providers/moyasar/webhook. This route is for the provider, not for customers. It has no segmnt session; it is checked with the provider’s own secret token. What happens on every verified event:
  1. The provider’s secret token is checked with a constant-time compare.
  2. The webhook body is never trusted. We read the merchant invoice back from the provider API.
  3. The amount and currency are checked against the plan’s fixed price (plus 15% VAT when VAT mode is on).
  4. The payment, the 15% VAT invoice, the subscription and the account plan are written in one batch, so the event is applied once and cannot be applied half way.
This makes the webhook idempotent: a provider retries a webhook, so the same event can arrive more than once. One provider invoice is recorded at most once, a second delivery answers 204 and does not change the plan again or extend the period. A refund reverses an applied payment. When the provider reports a refunded payment, the payment is recorded as refunded, the subscription is set to canceled, and the account returns to the free plan. The invoice row is kept: a refund is not a delete, and the fiscal sequence must stay gapless (a ZATCA credit note is a later open point). A refund is idempotent too, so a retried refund changes nothing. Answers: A verified event that does not match is refused with 400 and changes no plan. This covers a wrong amount, a currency that is not SAR, a missing plan or account in the invoice, an unknown plan, and an account we do not have.

4. Check the plan after payment

After a confirmed payment, GET /v1/billing/plan returns the paid plan, an active subscription period, the new limits and your usage. The call is documented on the Usage and plan limits page.
Notes:
  • limits is the paid plan’s limits (stored hours, watched minutes, domains, quality and the logo). In limits, null means no limit.
  • usage is your account now: stored seconds, watched minutes in the current period, and verified domains.
  • The new-upload and project limits are enforced, but they are not in this answer yet. See Usage and plan limits for the full limits table.
The paid period is one calendar month from the payment, in UTC. A shorter month is clamped, so 31 January becomes 28 or 29 February.

VAT

VAT is not on yet. Today VAT is off:
  • When VAT is off, the customer pays the plan price and the invoice shows 0 VAT.
  • When VAT mode is on, the amount is the plan price plus 15%, and the invoice records the VAT.
The webhook checks the paid amount with the same rule, so a payment that does not match the expected amount is refused. VAT is ZATCA’s money, not segmnt revenue. The stored invoice lines carry the VAT: each line has unitAmount (net), vatAmount and amount (gross, net + VAT). So the lines add up to invoices.total when VAT mode is on.

Not live yet

  • The annual plan. Only the monthly plan is on sale.
  • The invoice PDF. The 15% VAT invoice is written, but its PDF file is not built yet.

Errors

Errors use application/problem+json. Use the code field in your code, not the text. The type field links to https://docs.segmnt.cloud/errors/{code}.

Tested in TEST

Checked against https://test-api.segmnt.cloud on 14 Oct 2026:
  • GET /v1/billing/plans with no session -> 401 unauthorized.
  • POST /v1/billing/checkout with no session -> 401 unauthorized.
  • POST /v1/billing/providers/moyasar/webhook with no webhook secret configured -> 503 billing_unavailable.
  • POST /v1/billing/checkout with an unknown plan -> 400 invalid_request (unit test).
  • POST /v1/billing/checkout with an unknown, expired or used-up discountCode -> 400 invalid_request (unit test).
The paid path (a verified invoice applies the plan, the 15% VAT invoice, sequential invoice numbers, idempotent replay, and every 400 refusal path) is proved by the API tests in api/src/billing.test.ts. The same tests cover the discount paths: a founding code (30%), a referral code (10%), the 40% cap, the plan line plus the negative discount line that still reconcile to the total, and a replay that does not count the code twice. The live Moyasar sandbox run needs the payment provider’s sandbox keys, so the end-to-end provider call is not run on TEST yet.