# Stream App API Documentation > Complete API documentation and guides for Stream App ## Quick Start for LLMs > Read this section first. It is written for AI agents and coding assistants integrating with Stream (streampay.sa), a Saudi payment platform for payment links, invoices, subscriptions, installments, coupons, and a hosted customer billing portal. Everything below is derived from the guides and API reference listed in the Table of Contents further down. ### Mental model (read before writing any code) - **Default flow:** Your server -> `POST /api/v2/payment_links` -> redirect the customer (or embed) to the returned `url` -> Stream hosted checkout -> customer lands on your `success_redirect_url` / `failure_redirect_url` -> **you verify server-side** and/or receive webhooks. - **Payment links are the recommended way to collect money.** The invoice and subscription create endpoints say so themselves: "For most use cases, use the Checkout/Payment Link API instead." Do not default to `POST /invoices` or `POST /subscriptions`. - **Entities are created only after a successful payment.** A paid link creates: - one-time products -> an **invoice** + **payment(s)** - recurring products -> a **subscription** + initial **invoice** + **payment** - A failed attempt creates nothing (no invoice, payment, or subscription). - **Recurrence lives on the product, not the link.** Create the product as one-time or recurring first, then reference it by `product_id`. - **"Consumer" = "customer".** The API says consumer; the dashboard says customer. The consumer `id` is what you pass as `organization_consumer_id`. ### Base URL and authentication - Base URL: `https://stream-app-service.streampay.sa/api/v2` - Header on every request: `x-api-key: ` - Build the token: `echo -n 'api-key:api-secret' | base64` - Create the key pair in the dashboard under Settings -> API Keys. The secret is shown at creation; store it like a password. - Never put the key pair or token in browser/mobile code. All calls go through your backend. - Optional header `x-branch-id: ` selects the branch. If omitted, the key's default branch is used. Keys only reach branches they were granted (or all branches if created with `is_all_branches`). Unauthorized branch -> 4xx. - The MCP server uses a different header: `Authorization: Bearer ` (see MCP section). ### The five-step integration **1. (Optional) Create a consumer** - `POST /api/v2/consumers` Only `name` is required. Recommended: `phone_number` (E.164, e.g. `+966501234567`) or `email` so Stream can notify them, plus `external_id` for your own ID. ```bash curl -X POST https://stream-app-service.streampay.sa/api/v2/consumers \ -H "x-api-key: $STREAM_TOKEN" -H "Content-Type: application/json" \ -d '{"name":"Ahmad Ali","email":"ahmad@example.com","phone_number":"+966501234567","external_id":"cust_12345"}' ``` Returns the consumer; keep `id`. Duplicate phone/email -> `DUPLICATE_CONSUMER`: list consumers and reuse the existing `id`. **2. Have a product** - `POST /api/v2/products` (one-time or recurring). Keep its `id`. A product can hold one active price per currency (`POST /products/{product_id}/prices` adds another currency). **3. Create the payment link** - `POST /api/v2/payment_links` ```bash curl -X POST https://stream-app-service.streampay.sa/api/v2/payment_links \ -H "x-api-key: $STREAM_TOKEN" -H "Content-Type: application/json" \ -d '{ "name": "Order 789", "items": [{"product_id": "PRODUCT_UUID", "quantity": 1}], "contact_information_type": "PHONE", "currency": "SAR", "max_number_of_payments": 1, "organization_consumer_id": "CONSUMER_UUID", "success_redirect_url": "https://example.com/success", "failure_redirect_url": "https://example.com/failure", "custom_metadata": {"order_id": "789"} }' ``` - Response includes `url`, `id`, `status` (e.g. `ACTIVE`), `amount` (decimal string like `"2500.00"`), `currency`. Redirect the customer to `url`. - Omit `organization_consumer_id` for a shareable link: each payer enters their own details and a consumer is auto-created. Use a higher `max_number_of_payments` for these (docs example: 100). - Set `organization_consumer_id` + `max_number_of_payments: 1` for a link tied to one known customer; the checkout skips the details form. **4. Handle the redirect, then verify.** Stream appends query params (`status`, `message`, `payment_link_id`, and on success `invoice_id`, `payment_id`, `consent_id`, `organization_consumer_id`). **Do not trust them.** Fetch `GET /api/v2/invoices/{invoice_id}` and check `status`, `amount`, `currency` against your own order before fulfilling. **5. Confirm asynchronously with webhooks** (recommended) and/or poll `GET /api/v2/payments/{id}` and `GET /api/v2/subscriptions/{id}`. The `status` field on the fetched object is the source of truth. ### Embedded checkout (instead of redirect) Create the payment link on your server, pass only the `url` to the page: ```html
``` Optional: `minHeightPx` (280), `maxHeightPx` (920), `vhCapPx` (800), `vhFraction` (0.7). Apple Pay in embedded mode requires hosting Stream's domain-association file at `/.well-known/apple-developer-merchantid-domain-association` on your domain and contacting Stream support to register you. ### Webhooks in 60 seconds - Register endpoints in the dashboard (Settings -> Webhooks) and pick event types. There is also a `POST /api/v2/webhooks` endpoint. - Body shape: `event_type`, `entity_type`, `entity_id`, `entity_url`, `status`, `data`, `timestamp`. `data.metadata` echoes your `custom_metadata`. - Headers: `X-Webhook-Event`, `X-Webhook-Entity-Type`, `X-Webhook-Entity-ID`, `X-Webhook-Timestamp`, `X-Webhook-Signature` (`t={timestamp},v1={signature}`). - **Verify the signature:** HMAC-SHA256 over the exact string `"{timestamp}.{raw_request_body}"` using the webhook's `secret_key`, hex digest, constant-time compare to `v1`. Use the **raw bytes** of the body, not re-serialized JSON. ```python import hmac, hashlib def verify(secret: str, raw_body: bytes, header: str) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) msg = f"{parts['t']}.{raw_body.decode()}" expected = hmac.new(secret.encode(), msg.encode(), hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts["v1"]) ``` - Respond with any 2xx quickly. Non-2xx triggers up to 5 retries at roughly 5 min, 30 min, 2 h, 6 h, 12 h; after that the delivery is marked failed. ### Testing - Use the sandbox environment with Moyasar test cards. Name: any two words. Expiry: any future date. CVC: any 3 digits (4 for AMEX). - Success: Mada `4201320111111010`, Visa `4111111111111111`, Mastercard `5421080101000000`, AMEX `340000000900000`. - Insufficient funds: Mada `4201320000311101`, Visa `4123120001090000`, Mastercard `5457210001000092`, AMEX `340033000000000`. - Generic decline: Mada `4201321234411220`, Visa `4123120001090109`. - 3DS failure cases exist for Visa only (see Testing Cards guide). - **Apple Pay in sandbox is driven by amount:** 200.00-300.00 SAR succeeds; 1000.00-1700.00 SAR hits assorted failures; **any other amount fails.** Do not use random amounts when testing Apple Pay. --- ## Tips and Tricks for LLMs ### Hard rules (violating these causes real bugs or lost money) 1. **Never mix one-time and recurring products in one payment link.** Split into two links tied to one internal order (store a shared order reference in `custom_metadata` or your DB), run them sequentially, and mark the order complete only after both succeed. 2. **Never fulfill an order from redirect query params alone.** Verify via `GET /invoices/{invoice_id}` (status, amount, currency) or a verified webhook. 3. **Never expose the key pair, the Base64 token, or a webhook `secret_key` client-side.** Embedded checkout only needs the link `url`. 4. **Always verify `X-Webhook-Signature`** and reject missing or invalid ones. 5. **Make webhook handlers idempotent.** Retries and multi-event flows mean you will see duplicates and out-of-order delivery. Key on `entity_id` + `event_type`. 6. **Do not use the redirect `id` param as a Stream ID.** It is the payment gateway's ID, for support debugging only. Use `payment_id` or `invoice_id`. ### Choosing the right endpoint | Goal | Use | |---|---| | Collect a one-off payment | `POST /payment_links` with one-time product(s) | | Start a subscription | `POST /payment_links` with recurring product(s) | | Take payment via your own page | `POST /payment_links` + Embedded Checkout | | Record money received offline (cash, bank transfer, etc.) | Create invoice, then `POST /payments/{id}/mark-paid` | | Send customer to self-service billing | `POST /consumer_portal/sessions` | | Charge a saved card on demand | `POST /payments/auto-charge-on-demand/{payment_id}` (restricted; only some orgs) | | Pause billing | `POST /subscriptions/{id}/freeze` | | Reverse a payment | `POST /payments/{id}/refund` | ### Consumers - Check for an existing consumer before creating one (match by `external_id`, or `GET /consumers?search_term=...`) to avoid `DUPLICATE_CONSUMER`. - Phone numbers must be E.164. `+966501234567`, not `0501234567`. - `communication_methods` accepts `WHATSAPP`, `EMAIL`, `SMS`. Provide at least a phone or email, otherwise Stream cannot notify the customer. - `DELETE` on a consumer is a soft delete (`is_deleted`). ### Payment links - `contact_information_type` is `PHONE` or `EMAIL` (shown in docs examples) and controls which contact the checkout collects/uses for login. - Put your own references in `custom_metadata` (arbitrary JSON, nested allowed). It comes back in webhook `data.metadata`, which is the cleanest way to join Stream events to your orders. - `language=en` or `language=ar` as a query param on the link URL switches the checkout UI language only. Default is Arabic (or the org's configured language). - Installments: pass `payment_methods: {"installment": true|false}` on the link to override the org default. Omit `payment_methods` to inherit the org setting. - Link coupons and status are managed after creation with `PATCH`-style endpoints: Update Payment Link Coupons, Update Payment Link Status. To retire a link, update its status rather than deleting. - Failed payment-link attempts produce **no** invoice/payment/subscription. To catch these, subscribe to `PAYMENT_LINK_PAY_ATTEMPT_FAILED`, not `PAYMENT_FAILED`. - Redirect `status` values: `paid`, `pending`, `failed`, `failed_internal_error`. `failed` = definite decline, let the payer retry. `failed_internal_error` = unclear (timeout etc.); money may or may not have moved, so tell the payer to contact support and reconcile via the API before retrying or refunding. ### Installments - Customers choose 2, 3, or 4 installments at checkout. **Installments are disabled automatically for recurring products.** - Each installment is its own payment with its own due date. Default split period is monthly; it cannot be changed via API or dashboard (contact support). - Minimum is **100 SAR per installment**. A 250 SAR order allows 2 installments but not 3. This minimum is also support-only to change. - Rounding remainder goes to the **last** installment (999.98 / 3 -> 333.33, 333.33, 333.32). - Webhooks: one `PAYMENT_SUCCEEDED` per installment paid; `INVOICE_COMPLETED` only fires once **every** installment is paid. - Invoice-level installment settings are configured in the dashboard (invoices are created from the UI per the guide); payment-link defaults live in Settings -> Payment Settings. ### Subscriptions - Cancel (`POST /subscriptions/{id}/cancel`): an `ACTIVE` subscription is **scheduled to cancel at period end** and stays active until then. `INACTIVE`, `TRIALING`, and `TRIAL_PENDING` subscriptions cancel **immediately**. Pass `cancel_related_invoices` to also cancel ongoing invoices. Undo a pending cancellation with the Uncancel endpoint. - Update (`PUT /subscriptions/{id}`): - Items, subscription-level coupons, and recurring interval changes are **deferred to `current_period_end`** as a "pending change". The response includes `pending_change` with `next_invoice_preview` and a per-line diff. - If the next-cycle invoice already exists and is unpaid, it is cancelled and reissued at the new amount. - Description, auto-cancel cycle count (`until cycle number`), and payment-method overrides apply **immediately**. - You cannot change the payer. Cancel and create a new subscription instead. - Revert a scheduled change with `DELETE /subscriptions/{id}/pending-change` (returns 200 even if nothing was scheduled). - Freeze pauses **invoice generation** for a date range and auto-resumes afterward. Freezes have their own list / update / delete endpoints. - Renewal webhook sequence: `INVOICE_CREATED` -> `PAYMENT_SUCCEEDED` -> `INVOICE_COMPLETED` (plus `SUBSCRIPTION_CYCLE_RENEWED_SUCCESSFULLY`). On `INVOICE_COMPLETED`, check `subscription_id` in the payload to recognize a renewal. Failed automatic renewals emit `SUBSCRIPTION_CYCLE_RENEWAL_FAILED`. - Plan-change event family: `SUBSCRIPTION_PLAN_CHANGE_SCHEDULED` (deferred), `..._CHANGED` (deferred change applied), `..._CHANGE_CANCELED`, `..._CHANGE_INVOICE_REISSUED`, and `SUBSCRIPTION_PLAN_UPDATED` (immediate change such as seats, add-ons, cycle count). ### Payments, refunds, and manual payments - Multiple refunds per payment are supported. Partial refund -> payment status `PARTIALLY_REFUNDED` + `PAYMENT_PARTIALLY_REFUNDED`. Cumulative total reaches the payment amount -> `REFUNDED` + `PAYMENT_REFUNDED`. **The invoice status does not change on refund.** Subscribe to both refund events if you handle partials. - Webhook refund payloads carry `amount_refunded` (this refund) and `total_amount_refunded` (cumulative). - If a payment belongs to a shared charge, set `payment_is_part_of_related_set: true` on the refund request to spread the refund across the related payments in invoice order. - Refund errors: `PAYMENT_REFUNDED_ALREADY` (duplicate, safe to treat as done), `PAYMENT_REFUNDED_FAILED` (processor failure, retryable later). - `mark-paid` supports methods `CASH`, `BANK_TRANSFER`, `CARD`, `QURRAH`. Omit `amount` (or equal to payment amount) for full. A partial amount reduces the original payment, marks it `SUCCEEDED`, and creates a new `PENDING` `SPLIT_REMAINDER` payment for the rest; the remainder must be more than 1 in the payment currency, and partial is not allowed while `UNDER_REVIEW`. The invoice completes only when every payment is paid. - Invoice in-place edits (`PATCH /invoices/{id}/inplace`) are for small changes such as description or scheduled date. A finalised invoice rejects actions with `INVOICE_FINALISED`. - Products and coupons used by a finalized invoice cannot be edited (`PRODUCT_USED_IN_FINALIZED_INVOICE`, `COUPON_USED_IN_FINALIZED_INVOICE`). Create a new one instead. Deleting a product price is a soft delete (archive). ### Lists, pagination, and filtering - Query params: `page` (starts at 1), `limit` (max 100), `sort_field` (default `created_at`), `sort_direction` (`asc`/`desc`, default `desc`), `search_term` (free text across names/IDs/descriptions). - Array filters repeat the key: `statuses=PENDING&statuses=PROCESSING`. In Python `requests`, pass a list. Resource-specific filters exist (e.g. `invoice_id` on payments). - Response: `{"data": [...], "pagination": {"total_count", "max_page", "current_page", "limit", "has_next_page", "has_previous_page"}}`. Loop on `has_next_page`; use `limit=100` to minimize calls. Note: the guide describes `total_count` as the count on the current page, so rely on `has_next_page` / `max_page` rather than assuming it is a global total. - When you apply filters, pagination metadata reflects the filtered set. ### Errors - Handled errors use `{"error": {"code": "...", "message": "...", "additional_info": "..."}}`. Branch on `code`, never on `message`. - Validation failures (HTTP 422) can instead return FastAPI-style `{"detail": [{"loc": [...], "msg": "...", "type": "..."}]}`. Handle **both** shapes. - Status mapping: 400 bad request, 401 unauthorized, 403 forbidden / permission, 404 not found, 410 gone, 422 unprocessable or duplicate. - Codes you should handle explicitly: `INVALID_PARAMETERS`, `DUPLICATE_CONSUMER`, `DUPLICATE_PAYMENT`, `PHONE_ALREADY_REGISTERED`, `INVOICE_FINALISED`, `PAYMENT_REFUNDED_ALREADY`, `PAYMENT_REFUNDED_FAILED`, `PERMISSION_FORBIDDEN`, `PRODUCT_USED_IN_FINALIZED_INVOICE`, `COUPON_USED_IN_FINALIZED_INVOICE`, `STREAM_ERROR`, `STREAM_UNKNOWN_ERROR`. On `STREAM_ERROR` / `STREAM_UNKNOWN_ERROR`, log `additional_info` and escalate to support rather than blind-retrying a money-moving call. ### Customer portal - Backend calls `POST /api/v2/consumer_portal/sessions` with `organization_consumer_id` (HTTP 201 -> `url`). The token is **short-lived and single-use**, so generate a fresh session each time the customer clicks "Manage billing". Do not cache or email the URL. - Optional body fields: `return_url` (a back-link hint only; Stream does **not** auto-redirect) and `subscription_id` (open directly on one subscription). The URL can be used for a redirect or an iframe. - The portal can only offer plan switches and add-ons if the dashboard catalog is configured: **product switch groups** (only members of the same group can replace each other; mixed MONTHLY/YEARLY allowed) and **add-on mappings** (which base products make an add-on eligible), plus the Customer Portal permission toggles. If the portal shows nothing to switch, the cause is configuration, not the API. - Subscriptions with subscription-level coupons generally cannot be edited from the portal. Proration is computed by Stream and excludes the current day; do not reimplement it. ### Handling your own customer notifications By default Stream notifies consumers (confirmations, invoice updates, subscription changes). To own all messaging: (1) ask Stream support to enable the setting that blocks default consumer notifications, (2) use webhooks for event-driven messages, (3) poll the List Payments / Invoices / Subscriptions endpoints with filters for scheduled messages like reminders and overdue notices. You cannot self-serve step 1. ### Branches Requests are scoped to one branch. Use `x-branch-id` to target a branch; without it the key's default branch is used. If resources seem "missing", you are probably querying the wrong branch. ### MCP server (for AI agents) - Hosted at `https://mcp.streampay.sa/mcp`. Config key `"url"`, plus `"headers": {"Authorization": "Bearer "}` for operational tools. - The resource `stream://docs/openapi` exposes the **full, current OpenAPI spec and needs no API key**. Read it before guessing request fields; this documentation set links endpoint pages whose rendered Markdown does not include the full request/response schemas. - Tool groups: payment links, customers, products, coupons, invoices (create, list, get, send, void), payments (list, get, refund). Tool names (e.g. `create_payment_link`, `refund_payment`) are not the same as REST paths. Note that some MCP descriptions (for example ZATCA-compliant invoices) are tool-level descriptions, so confirm behavior in the OpenAPI spec. ### SDKs TypeScript SDK (`StreamSDK.init(process.env.STREAM_API_KEY!)`) and an Express SDK (a `Checkout({ apiKey, ... })` route handler). Full docs live in their GitHub repositories, linked at the bottom of the Table of Contents. SDKs take the same `x-api-key` token. ### Gotchas and doc inconsistencies to be aware of - **The Installments link in the Table of Contents is malformed** (`https://docs.streampay.sa/docs/guides/installments.md`). The correct path is most likely `https://docs.streampay.sa/docs/guides/installments.md`. The installment facts above come from the same guide as published in the developer-docs full export. - The Authentication guide says "HTTP Basic Authentication" but the actual mechanism is the **`x-api-key` header carrying the Base64 token**, not an `Authorization: Basic` header. Use `x-api-key`. - The Authentication guide's curl example posts `{"name","amount"}` to `/payment_links`; that is a placeholder. A real payment link requires `items` with `product_id`s, as in the Getting Started examples. - The Pagination guide's example hits `/api/v2/customers`; the real resource path is **`/api/v2/consumers`**. - The Branches guide example calls `/api/v2/user`; the identity endpoint listed in the API reference is "Get User And Organization Info" (`/api/v2/me`-style). Check the reference for the exact path. - The branch create path may differ between exports (`/api/v2/branch` vs the public reference's "Create Branch"). Confirm in the OpenAPI spec. - Sandbox vs production: the docs reference a sandbox environment and test cards but do not publish a separate sandbox base URL. Use the sandbox-mode key/dashboard Stream gives you, or ask support, rather than inventing a hostname. - Amounts in responses appear as **decimal strings in SAR** (e.g. `"2500.00"`). Compare as decimals, not floats. - When unsure about a field name, enum value, or required property, do not guess. Read `stream://docs/openapi` via the MCP server or the endpoint's page in the Table of Contents. ### Suggested agent workflow for "add Stream payments to my app" 1. Ask which flow: one-time, subscription, installments, or embedded/redirect. Confirm whether the cart ever mixes one-time and recurring items. 2. Create (or look up) the product(s), and the consumer if the customer is known. 3. Create the payment link from the backend; return only `url` to the client. 4. Implement the success/failure routes: read `invoice_id`, call `GET /invoices/{id}`, check `status`/`amount`/`currency`, then fulfill. 5. Implement the webhook endpoint with raw-body signature verification, idempotency, and fast 2xx. Subscribe to at minimum `PAYMENT_SUCCEEDED`, `PAYMENT_FAILED`, `INVOICE_COMPLETED`, plus `PAYMENT_PARTIALLY_REFUNDED` / `PAYMENT_REFUNDED` and the `SUBSCRIPTION_*` events if you sell subscriptions. 6. Test with the sandbox cards above (and Apple Pay amount ranges) before going live. --- ## Getting Started with Stream Welcome to **Stream**, your partner for seamless and secure online payments. This guide walks you through the essentials to integrate our **checkout experience** into your website or app. The full payment flow: > Your site → Stream Secure Checkout → Redirect to your success or failure page :::tip Using an AI assistant? Point it at [`/llms.txt`](pathname:///llms.txt) for an LLM-friendly index with integration instructions (full content in [`/llms-full.txt`](pathname:///llms-full.txt)). Any docs page is also available as Markdown by appending `.md` to its URL (for example `/webhooks.md`) or by requesting it with `Accept: text/markdown`. The [MCP server](/mcp-server) gives agents direct tool access. ::: ## Overview: Integration Steps 1. [Authorization](#authorization) 1. [Create a Customer (Consumer)](#create-a-customer-consumer) 1. [Creating a Payment Link](#creating-a-payment-link) 1. [Handling Payment Redirects](#handling-payment-redirects) 1. [Retrieve Payment or Subscription Status](#retrieve-payment-or-subscription-status) --- ## Authorization All API calls require authentication using your Stream API Key Pair (`api-key` + `api-secret`). 1. Navigate to Stream [API Settings](https://app.streampay.sa/settings/api-keys). 1. Click **Create Key Pair** and store the keys securely. To authenticate your requests, encode your API key and secret as a Base64-encoded token. Use the following command (replace `api-key` and `api-secret` with your actual values): ```bash echo -n 'api-key:api-secret' | base64 ``` Include this Base64-encoded token in the `x-api-key` authentication header for all API requests: ``` x-api-key: ``` --- ## Create a Customer (Consumer) Before you can generate a payment link for a specific customer, you must first create a customer (consumer) record in Stream. This step registers the customer in your organization and returns a unique identifier that you'll use to tie payment links to that customer. ### Why Create a Customer First? - **Personalized checkout**: When you attach a customer to a payment link, Stream skips the customer-details form during checkout because the customer's information is already known. - **Customer tracking**: Every invoice, subscription, and payment is linked back to the customer record, making reconciliation straightforward. - **Repeat billing**: For recurring products, the customer record is required to manage the subscription lifecycle. ### Step 1: Prepare the Customer Data Decide which contact fields to include. The only **required** field is `name`. All other fields are optional but recommended for a complete integration. | Field | Type | Required | Description | | ------------------------- | ---------- | -------- | --------------------------------------------------------------------------- | | `name` | string | **Yes** | Full name of the customer | | `phone_number` | string | No | Phone number (E.164 format, e.g., `+966501234567`) | | `email` | string | No | Email address | | `external_id` | string | No | Your own system's ID for this customer (useful for cross-referencing) | | `iban` | string | No | IBAN (max 34 characters) for bank-related operations | | `alias` | string | No | Short alias or display name | | `comment` | string | No | Internal notes about the customer | | `preferred_language` | string | No | Customer's preferred language for communications | | `communication_methods` | string[] | No | Preferred channels: `WHATSAPP`, `EMAIL`, `SMS` (can include multiple) | > **Tip**: Always include at least `phone_number` or `email` so Stream can send payment notifications to the customer. ### Step 2: Send the Create Request - **Endpoint**: `POST /api/v2/consumers` - **Auth**: `x-api-key: ` **cURL example:** ```bash curl -X POST https://stream-app-service.streampay.sa/api/v2/consumers \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Ahmad Ali", "email": "ahmad@example.com", "phone_number": "+966501234567", "external_id": "cust_12345", "communication_methods": ["EMAIL", "SMS"] }' ``` ### Step 3: Handle the Response A successful response (`200 OK`) returns the full customer record. The most important field is `id` — this is the `organization_consumer_id` you'll use when creating payment links. **Example response (simplified):** ```json { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Ahmad Ali", "email": "ahmad@example.com", "phone_number": "+966501234567", "external_id": "cust_12345", "is_deleted": false, "created_at": "2025-06-15T10:30:00Z", "communication_methods": ["EMAIL", "SMS"], "preferred_language": "ar" } ``` > See the full response schema in the [Create Consumer API Reference](/api/v2-consumers-create). ### Step 4: Handle Errors If the request fails validation, the API returns a `422` status with an array of error details: ```json { "detail": [ { "loc": ["body", "name"], "msg": "Field required", "type": "missing" } ] } ``` Each object in the `detail` array contains `type` (error kind), `loc` (field path), and `msg` (human-readable message). Common error scenarios: | `type` / Scenario | `loc` | `msg` (example) | Resolution | | ------------------ | ----- | ---------------- | ---------- | | `missing` | `["body", "name"]` | `"Field required"` | Include the required `name` field in the request body | | `value_error` | `["body", "phone_number"]` | `"value is not a valid phone number"` | Use E.164 format: `+` (e.g., `+966501234567`) | | `value_error` | `["body", "email"]` | `"value is not a valid email address"` | Provide a properly formatted email address | The API may also return a non-validation error with a top-level error code instead of a `detail` array. The most common one: | Error Code | Cause | Resolution | | ---------- | ----- | ---------- | | `DUPLICATE_CONSUMER` | A consumer with the same phone number or email already exists | Use [Get All Consumers](/api/v2-consumers-list) to find the existing record and use its `id` instead | > **Tip**: Always check for an existing consumer before creating a new one to avoid `DUPLICATE_CONSUMER` errors. You can list consumers with `GET /api/v2/consumers` or match by `external_id` in your own system. ### Step 5: Use the Customer ID in a Payment Link Once you have the customer `id` from the response, pass it as `organization_consumer_id` when [creating a payment link](#creating-a-payment-link). This ties the payment to the specific customer and skips the customer-details step at checkout. > **Note**: If you omit `organization_consumer_id`, the payment link will collect customer details during checkout and create a new customer record automatically. Use this approach for general-purpose links shared with multiple customers. ### Managing Existing Customers After creating customers, you can manage them with these endpoints. The `{consumer_id}` path parameter is the `id` returned by `POST /api/v2/consumers` — the same value you pass as `organization_consumer_id` when creating payment links. | Action | Method | Endpoint | API Reference | | ---------- | -------- | ------------------------------------ | ------------------------------------- | | List all | `GET` | `/api/v2/consumers` | [Get All Consumers](/api/v2-consumers-list) | | Get one | `GET` | `/api/v2/consumers/{consumer_id}` | [Get Consumer](/api/v2-consumers-get) | | Delete | `DELETE` | `/api/v2/consumers/{consumer_id}` | [Delete Consumer](/api/v2-consumers-delete) | Before creating a new customer, you can check for an existing record by listing consumers or using your `external_id` to avoid duplicates. --- ## Creating a Payment Link Create a checkout/payment link using the [Payment Link API](/api/v2-payment-links-create). - **Endpoint**: `POST /api/v2/payment_links` - **Auth**: `x-api-key: ` Payment links redirect customers to Stream's secure checkout page. They support two use cases: 1. **For multiple customers**: Create a general-purpose link that anyone can use. Each customer enters their information during checkout. Perfect for events, group purchases, or public product sales. 1. **For one specific customer**: Create a link tied to a specific customer by setting `organization_consumer_id`. The checkout page will skip the customer details collection step since the customer information is already known. Use this when charging a known customer. When a customer pays through a payment link, the behavior depends on the product types you include: - **One-time products**: When the payment link is paid, Stream creates an invoice with the products. The payment can be split into multiple installments, but the product itself is a one-time purchase (not recurring). - **Recurring products**: When the payment link is paid, Stream creates a subscription (with recurring billing) and an initial invoice. The subscription will automatically generate invoices for future billing cycles based on the recurring interval. > **Important**: You cannot mix one-time and recurring products in the same payment link. All products must be either one-time or all must be recurring. ### Strategy: Single Order with Both One-Time + Recurring Items If a cart contains both one-time and recurring products, split it into **two payment links** and guide the customer through a short two-step checkout. This keeps each payment link valid while preserving a single “order” in your system. **General approach** - Create one link that contains only **one-time** items and a separate link that contains only **recurring** items. - Treat both links as part of the same internal order (e.g., by storing a shared order reference in metadata or your database). - Present the links sequentially (or based on your business preference) so the customer completes both steps. - Confirm both outcomes via your normal status checks or webhooks before marking the order as complete. > **Note**: You'll need existing products to create payment links. See the [Products API](/api/v2-products-create) for creating products. If you want to link a payment to a specific customer, you'll also need to create a customer first using the [Consumers API](/api/v2-consumers-create). ### Example: Payment Link for Multiple Customers This example creates a payment link that can be used by multiple different customers. Each customer will enter their information when they visit the checkout page. ```json { "name": "Event Registration", "description": "Annual conference registration", "items": [ { "product_id": "your-product-uuid-here", "quantity": 1 } ], "contact_information_type": "PHONE", "currency": "SAR", "max_number_of_payments": 100, "success_redirect_url": "https://example.com/success", "failure_redirect_url": "https://example.com/failure", "custom_metadata": { "event_id": "conf_2025", "campaign_source": "email_marketing" } } ``` **Code snippet (general-purpose, one-time product):** ```bash curl -X POST https://stream-app-service.streampay.sa/api/v2/payment_links \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Event Registration", "description": "Annual conference registration", "items": [ { "product_id": "your-product-uuid-here", "quantity": 1 } ], "contact_information_type": "PHONE", "currency": "SAR", "max_number_of_payments": 100, "success_redirect_url": "https://example.com/success", "failure_redirect_url": "https://example.com/failure" }' ``` > **Note**: `organization_consumer_id` is **not set**, so the link will collect customer information from each payer. ### Example: Payment Link for One Specific Customer This example creates a payment link for a specific customer. When you specify `organization_consumer_id`, the checkout page will skip the customer details collection step since the customer information is already known. ```json { "name": "Product Purchase", "description": "One-time payment for product order", "items": [ { "product_id": "your-product-uuid-here", "quantity": 1 } ], "contact_information_type": "PHONE", "currency": "SAR", "max_number_of_payments": 1, "organization_consumer_id": "your-customer-uuid-here", "success_redirect_url": "https://example.com/success", "failure_redirect_url": "https://example.com/failure", "custom_metadata": { "customer_id": "cust_12345", "order_id": "order_789" } } ``` > **Note**: `organization_consumer_id` **is set**, so the checkout page will skip the customer details collection step. The `max_number_of_payments` is typically set to `1` when targeting a specific customer. ### Example: Payment Link for a Recurring Product (New Subscription) Use this when you want a customer to start a subscription from checkout. The product itself must be **recurring** (e.g., monthly), which is configured when you create the product via the [Products API](/api/v2-products-create). The payment link request is **the same endpoint** as one-time links: - **Endpoint**: `POST /api/v2/payment_links` - **Auth**: `x-api-key: ` When the customer completes payment, Stream automatically creates a **subscription** and an **initial invoice**. Future invoices are generated based on the product's recurring interval. You can retrieve the created subscription via the [Subscriptions API](/api/v2-subscriptions-list). ```json { "name": "Premium Plan Subscription", "description": "Monthly recurring plan", "items": [ { "product_id": "recurring-product-uuid-here", "quantity": 1 } ], "contact_information_type": "EMAIL", "currency": "SAR", "max_number_of_payments": 1, "organization_consumer_id": "your-customer-uuid-here", "success_redirect_url": "https://example.com/success", "failure_redirect_url": "https://example.com/failure", "custom_metadata": { "plan": "premium_monthly", "source": "pricing_page" } } ``` **cURL example:** ```bash curl -X POST https://stream-app-service.streampay.sa/api/v2/payment_links \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Premium Plan Subscription", "description": "Monthly recurring plan", "items": [ { "product_id": "recurring-product-uuid-here", "quantity": 1 } ], "contact_information_type": "EMAIL", "currency": "SAR", "max_number_of_payments": 1, "organization_consumer_id": "your-customer-uuid-here", "success_redirect_url": "https://example.com/success", "failure_redirect_url": "https://example.com/failure" }' ``` > **Notes**: > - The recurring behavior is defined on the **product**, not the payment link. > - Do not mix one-time and recurring products in the same payment link. > - If `organization_consumer_id` is omitted, the checkout will collect customer details and the subscription will be created for that newly created customer. ### Response The API returns a payment link object. The most important field is `url`, which you'll redirect customers to: ```json { "url": "https://streampay.sa/s/...", "id": "payment-link-uuid", "status": "ACTIVE", "amount": "2500.00", "currency": "SAR" } ``` > **Note**: This response has been simplified for brevity. The full response includes additional fields like `items`, `custom_metadata`, `success_redirect_url`, and more. See the [API Reference](/api/v2-payment-links-create) for the complete response schema. Redirect your customer to the `url` field to complete the payment securely. ### Checkout Language The checkout page language can be overridden using the `language` query parameter on the payment link URL. By default, the checkout page is displayed to the customer in the language the organization is configured in. Example: ``` https://streampay.sa/s/...?...&language=en ``` Or: ``` https://streampay.sa/s/...?language=en ``` Supported values: | Value | Language | | ------- | ---------- | | `ar` | Arabic (default) | | `en` | English | > Note: This parameter only affects the hosted checkout page UI language. Data is not affected. --- ## Handling Payment Redirects After checkout, Stream can redirect the customer to your `success_redirect_url` or `failure_redirect_url` (if provided) with payment information as query parameters. If you omit these URLs, Stream will show its hosted success/failure pages instead. ### Redirect URL Examples **Success redirect:** ``` https://example.com/success?id=...&invoice_id=...&status=paid&message=APPROVED ``` **Failure redirect:** ``` https://example.com/failure?payment_link_id=...&id=...&status=failed&message=... ``` ### Query Parameters All redirects include these parameters: | Parameter | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `id` | Payment gateway payment ID - for internal use only. Useful for debugging when contacting support, but cannot be used in Stream APIs. Use `payment_id` or `invoice_id` instead. | | `payment_link_id` | UUID of the payment link that was used | | `status` | Payment status: `paid`, `pending`, `failed`, or `failed_internal_error` | | `message` | Status message from the gateway (e.g., `APPROVED`, `DECLINED`, `The customer’s bank has declined.`) | | `login_method` | Consumer's preferred login method: `PHONE` or `EMAIL` | :::info - **`status=failed`**: A definite rejection (e.g. insufficient funds, card declined). The payer should **retry** after addressing the issue. - **`status=failed_internal_error`**: Transient or unclear failures (e.g. timeouts). The payer should **contact support**; money may or may not have been taken. ::: **Additional parameters on success only:** | Parameter | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------ | | `invoice_id` | UUID of the invoice created from the payment link | | `consent_id` | UUID of the payment consent/authorization | | `payment_id` | UUID of the payment record in Stream's system | | `organization_consumer_id` | UUID of the customer who made the payment (newly created if not set when the payment link was created) | > **Note**: On failure, the additional parameters above are not included since the payment did not complete successfully. ### Processing the Redirect Parse the query parameters from the redirect URL on your server. :::danger **Important:** Always validate the payment server-side before accepting the order or completing any business action. Fetch the invoice using the `invoice_id` from the redirect with the [Invoice API](/api/v2-invoices-get), and verify its `status`, `amount`, and `currency` match your expectations. This server-side verification ensures the payment is legitimate before updating your system state. Never trust redirect parameters alone, always verify through our API. ::: --- ## Retrieve Payment or Subscription Status Use the **Get Payment** and **Get Subscription** endpoints to fetch the latest status programmatically. **Endpoints** - **Payment status:** `GET /api/v2/payments/{payment_id}` → [Payments API](/api/v2-payments-get) - **Subscription status:** `GET /api/v2/subscriptions/{subscription_id}` → [Subscriptions API](/api/v2-subscriptions-get) **Authentication** Use the same `x-api-key` header described in [Authorization](#authorization): ``` x-api-key: ``` ### Example: Fetch Payment Status (cURL) ```bash curl -X GET https://stream-app-service.streampay.sa/api/v2/payments/your-payment-id \ -H "x-api-key: " ``` ### Example: Fetch Subscription Status (cURL) ```bash curl -X GET https://stream-app-service.streampay.sa/api/v2/subscriptions/your-subscription-id \ -H "x-api-key: " ``` ### Example: Fetch Status in JavaScript (Node.js) ```js const API_BASE = "https://stream-app-service.streampay.sa/api/v2"; const API_KEY = process.env.STREAM_API_KEY_BASE64; // base64(api-key:api-secret) async function getPaymentStatus(paymentId) { const res = await fetch(`${API_BASE}/payments/${paymentId}`, { headers: { "x-api-key": API_KEY } }); if (!res.ok) throw new Error(`Payment fetch failed: ${res.status}`); const payment = await res.json(); return payment.status; } async function getSubscriptionStatus(subscriptionId) { const res = await fetch(`${API_BASE}/subscriptions/${subscriptionId}`, { headers: { "x-api-key": API_KEY } }); if (!res.ok) throw new Error(`Subscription fetch failed: ${res.status}`); const subscription = await res.json(); return subscription.status; } ``` > **Tip**: The `status` field in each response is the source of truth for the current state. See the full response schema in the linked API reference pages. --- ## Pagination & Filtering ## Pagination Pagination helps you efficiently navigate, sort, and filter large collections of data without fetching everything at once. This is especially useful for endpoints like: - [List Payments](/api/v2-payments-list) - [List Products](/api/v2-products-list) - [List Invoices](/api/v2-invoices-list) - [List Subscriptions](/api/v2-subscriptions-list) ## Filtering Filtering is another way to efficiently navigate and filter large collections of data without fetching everything at once. Whenever pagination is applied, then so is filtering. Filtering allows you to filter your fetched objects through specific features or conditions like queries. Whenever an endpoint may return many items, Stream uses a consistent pagination and filtering structure across all list responses. --- ## Request Parameters ### Pagination Parameters You can control pagination using the following query parameters: | Parameter | Type | Description | |------------------|--------|-----------------------------------------------------------------------------| | `page` | int | Page number starting from **1**. | | `limit` | int | Number of items per page. Maximum: **100**. | | `sort_field` | string | Field used to sort the results. | | `sort_direction` | string | Either `"asc"` or `"desc"` depending on sorting direction. | By default, `sort_field` is `created_at` and `sort_direction` is `desc`. ``` GET api/v2/products?page=1&limit=20&sort_field=created_at&sort_direction=desc ``` --- ### Filtering Parameters Filtering allows you to query objects based on specific attributes. When you apply filters, the `pagination` metadata in the response will reflect the total count of items *matching* your filter, not the total database count. | Parameter | Type | Description | |---------------|------------------|---------------------------------------------------------------------------------------------------| | `search_term` | `string` | A generic query string. The API searches relevant fields (e.g., names, IDs, descriptions) for matches. | | **Field Specific**| `string` / `array` | Specific attributes depending on the resource (e.g., `invoice_id`, `statuses`, `currency`). | **Note on Array Filters:** Some parameters, like `statuses`, accept multiple values. In the query string, this appears as repeating keys: `statuses=PENDING&statuses=PROCESSING` For example, the [List Payments](/api/v2-payments-list) endpoint takes filtering queries like `invoice_id` and `statuses` to filter down the returned payments. Example query: ``` GET api/v2/payments?page=1&limit=20&invoice_id=73b89d47-1bb1-443c-9a4c-f0ee23e22191&statuses=PENDING&statuses=PROCESSING ``` --- ## Pagination Response Structure Paginated responses include a `pagination` object that provides metadata about the current, previous, and next pages. | Field | Description | |--------------------|-----------------------------------------------------------------------------| | `total_count` | Number of objects returned in the current page. | | `max_page` | Maximum page number available. | | `current_page` | Page you are currently on. | | `limit` | Page size (echoes your request, capped at 100). | | `has_next_page` | Whether another page exists after this one. | | `has_previous_page`| Whether a page exists before this one. | Example response snippet: ``` { "data": [...], "pagination": { "total_count": 20, "max_page": 12, "current_page": 1, "limit": 20, "has_next_page": true, "has_previous_page": false } } ``` --- ## Examples (Python) Below are practical examples using Python's `requests` library. ### 1. Basic Pagination Fetching the first page of results with a custom limit. ```python API_KEY = "YOUR_API_KEY" BASE_URL = "https://stream-app-service.streampay.sa/api/v2/products" response = requests.get( BASE_URL, headers={"x-api-key": API_KEY}, params={ "page": 1, "limit": 20 } ) data = response.json() print(f"Showing page {data['pagination']['current_page']} of {data['pagination']['max_page']}") ``` ### 2. Global Search Filtering Using the `search_term` to find items matching a specific string (e.g., a customer name or partial ID). ```python API_KEY = "YOUR_API_KEY" BASE_URL = "https://stream-app-service.streampay.sa/api/v2/customers" response = requests.get( BASE_URL, headers={"x-api-key": API_KEY}, params={ "search_term": "JOHN DOE", # Searches for customer with that name "limit": 10 } ) results = response.json()["data"] print(f"Found {len(results)} matches for 'Al-Faisaliah'") ``` ### 3. Advanced Filtering (Lists & Specific Fields) Filtering by a specific `invoice_id` and multiple `statuses`. Note how the list of statuses is passed to the `params` dictionary. ```python API_KEY = "YOUR_API_KEY" BASE_URL = "https://stream-app-service.streampay.sa/api/v2/payments" # To filter by multiple statuses, pass them as a list payload_filters = { "page": 1, "limit": 50, "invoice_id": "73b89d47-1bb1-443c-9a4c-f0ee23e22191", "statuses": ["PENDING", "PROCESSING"] # Requests handles this as statuses=PENDING&statuses=PROCESSING } response = requests.get( BASE_URL, headers={"x-api-key": API_KEY}, params=payload_filters ) payments = response.json()["data"] for p in payments: print(f"Payment ID: {p['id']} | Status: {p['status']}") ``` ### 4. Iterating Through All Pages A robust pattern to fetch every item available. ```python API_KEY = "YOUR_API_KEY" BASE_URL = "https://stream-app-service.streampay.sa/api/v2/products" page = 1 all_products = [] while True: response = requests.get( BASE_URL, headers={"x-api-key": API_KEY}, params={"page": page, "limit": 100} # Maximize limit to reduce requests ) body = response.json() all_products.extend(body["data"]) # Stop if there are no more pages if not body["pagination"]["has_next_page"]: break page += 1 print(f"Successfully fetched {len(all_products)} products.") ``` --- --- ## Webhooks Webhooks allow your platform to receive real-time notifications from Stream when key events occur—such as payment successes, failures, or cancellations. This enables you to react to events programmatically, such as updating your database, sending notifications, or triggering internal workflows. Stream's webhook infrastructure is designed for reliability and includes features like: - Event-specific subscriptions - Secure payload signing - Delivery tracking for observability - Automatic retries using exponential backoff ## Creating a Webhook To create or manage your webhooks, go to [Webhook Settings](https://app.streampay.sa/settings/webhooks). There you can: - Register a new webhook endpoint - Select which event types you want to subscribe to - Enable, disable, or delete webhooks ## Webhook Flow Logic Understanding which webhooks are sent in different scenarios: ### Single Payment Succeeds When a single payment succeeds: 1. `PAYMENT_SUCCEEDED` webhook is sent (one per payment) 2. If the payment completes the invoice (all payments are now paid), `INVOICE_COMPLETED` webhook is also sent Example: Invoice with 1 payment of 1000 SAR - Payment succeeds → `PAYMENT_SUCCEEDED` webhook - Invoice completes → `INVOICE_COMPLETED` webhook ### Multiple Payments Succeed (Installments) When multiple payments succeed together (e.g., installments): 1. `PAYMENT_SUCCEEDED` webhook is sent for each payment that succeeded (one per payment) 2. If all payments complete the invoice, `INVOICE_COMPLETED` webhook is also sent Example: Invoice with 3 payments of 1000 SAR each - Payments 1, 2, 3 all succeed together: - `PAYMENT_SUCCEEDED` webhook for payment_1 - `PAYMENT_SUCCEEDED` webhook for payment_2 - `PAYMENT_SUCCEEDED` webhook for payment_3 - `INVOICE_COMPLETED` webhook Example: Invoice with 5 payments, payments 1-3 succeed together: - `PAYMENT_SUCCEEDED` webhook for payment_1 - `PAYMENT_SUCCEEDED` webhook for payment_2 - `PAYMENT_SUCCEEDED` webhook for payment_3 - No `INVOICE_COMPLETED` webhook (invoice not yet fully paid) ### Payment Fails When a payment fails: 1. `PAYMENT_FAILED` webhook is sent 2. No invoice completion webhook is sent ### Payment Refunded (Partial or Full) Stream supports multiple refunds on a single payment. Webhook events depend on whether the payment is fully refunded or only partially refunded: **Partial refund** (cumulative refunded amount is still less than the payment amount): 1. `PAYMENT_PARTIALLY_REFUNDED` webhook is sent 2. Payment status becomes `PARTIALLY_REFUNDED` 3. Invoice status does not change **Full refund** (cumulative refunded amount equals the payment amount): 1. `PAYMENT_REFUNDED` webhook is sent 2. Payment status becomes `REFUNDED` 3. Invoice status does not change Example: Payment of 1000 SAR - First refund of 300 SAR → `PAYMENT_PARTIALLY_REFUNDED` (`amount_refunded` = 300, `total_amount_refunded` = 300) - Second refund of 200 SAR → `PAYMENT_PARTIALLY_REFUNDED` (`amount_refunded` = 200, `total_amount_refunded` = 500) - Final refund of 500 SAR → `PAYMENT_REFUNDED` (`amount_refunded` = 500, `total_amount_refunded` = 1000) :::info `PAYMENT_REFUNDED` means the payment is **fully** refunded. If you care about partial refunds, also subscribe to `PAYMENT_PARTIALLY_REFUNDED`. ::: ### Payment Marked as Paid When a payment is manually marked as paid: 1. `PAYMENT_MARKED_AS_PAID` webhook is sent 2. If this completes the invoice, `INVOICE_COMPLETED` webhook is also sent ### Subscription Renewal When a subscription cycle renews: 1. New invoice created → `INVOICE_CREATED` webhook 2. Payment succeeds → `PAYMENT_SUCCEEDED` webhook 3. Invoice completes → `INVOICE_COMPLETED` webhook **Note:** For subscription renewals, the `INVOICE_COMPLETED` webhook indicates that the subscription cycle has been renewed successfully. The invoice will have a `subscription_id` in its payload, and you can check if it's a renewal invoice by comparing it with previous invoices for that subscription. ## Event Types ### Payment Events - `PAYMENT_SUCCEEDED` - Payment completed successfully - `PAYMENT_FAILED` - Payment attempt failed - `PAYMENT_CANCELED` - Payment was canceled - `PAYMENT_PARTIALLY_REFUNDED` - A refund was issued and the payment is still only partially refunded - `PAYMENT_REFUNDED` - Payment was fully refunded - `PAYMENT_MARKED_AS_PAID` - Payment was manually marked as paid ### Invoice Events - `INVOICE_CREATED` - New invoice created - `INVOICE_SENT` - Invoice sent to consumer - `INVOICE_ACCEPTED` - Invoice accepted by consumer - `INVOICE_REJECTED` - Invoice rejected by consumer - `INVOICE_COMPLETED` - Invoice completed (all payments done) - `INVOICE_CANCELED` - Invoice canceled - `INVOICE_UPDATED` - Invoice details updated ### Subscription Events - `SUBSCRIPTION_CREATED` - New subscription created - `SUBSCRIPTION_ACTIVATED` - Subscription changed to `ACTIVE` (initial activation or reactivation) - `SUBSCRIPTION_INACTIVATED` - Subscription deactivated - `SUBSCRIPTION_CANCELED` - Subscription canceled - `SUBSCRIPTION_FROZEN` - Subscription frozen - `SUBSCRIPTION_CYCLE_RENEWED_SUCCESSFULLY` - Renewal payment succeeded and the subscription was renewed - `SUBSCRIPTION_CYCLE_RENEWAL_FAILED` - Automated/tokenized renewal payment failed - `SUBSCRIPTION_CANCEL_AT_PERIOD_END` - Subscription scheduled to cancel at period end - `SUBSCRIPTION_FREEZE_NOW` - Subscription frozen immediately - `SUBSCRIPTION_UNFREEZE_NOW` - Subscription unfrozen immediately - `SUBSCRIPTION_UNFREEZE_FUTURE` - Subscription scheduled to unfreeze in future - `SUBSCRIPTION_FREEZE_CANCEL` - Subscription freeze canceled - `SUBSCRIPTION_PLAN_CHANGE_SCHEDULED` - A plan change was scheduled to take effect at `current_period_end`. - `SUBSCRIPTION_PLAN_CHANGE_CANCELED` - A previously scheduled plan change was canceled before it took effect. - `SUBSCRIPTION_PLAN_CHANGED` - The scheduled plan change was actually applied — items, coupons, interval, and amount are now live on the subscription. - `SUBSCRIPTION_PLAN_CHANGE_INVOICE_REISSUED` - While scheduling a plan change, an existing prepared next-cycle invoice was canceled and regenerated from the new target plan. This is triggered alongside `SUBSCRIPTION_PLAN_CHANGE_SCHEDULED` if it affects the plan's invoice. - `SUBSCRIPTION_PLAN_UPDATED` - Subscription plan change was applied immediately. This applies for changing seats, adding add-ons, and changing number of subscription cycles. ### Payment Link Events - `PAYMENT_LINK_PAY_ATTEMPT_FAILED` - Payment attempt failed for a payment link **Note:** This webhook is sent when a payment link payment fails. Unlike regular payment failures, no invoice, payment, or subscription entities are created on failure, so `PAYMENT_FAILED` webhook cannot be sent. This event exists specifically to notify about payment link failures when no entities exist. ## Webhook Payload The webhook POST request will have a JSON body like: ```json { "event_type": "PAYMENT_SUCCEEDED", "entity_type": "PAYMENT", "entity_id": "uuid", "entity_url": "", "status": "SUCCEEDED", "data": { /* payment or event-specific data */ }, "timestamp": "2025-07-15T14:41:21.705Z" } ``` ## Webhook Payload Example ```json { "data": { "invoice": { "id": "2df0f7e0-2634-46ab-829a-bfcb0a797d87", "url": "https://stream-app-service.streampay.sa/api/v2/invoices/2df0f7e0-2634-46ab-829a-bfcb0a797d87" }, "payment": { "id": "e2182d3d-b4cf-4972-bcc0-ec6d963c066d", "url": "https://stream-app-service.streampay.sa/api/v2/payments/e2182d3d-b4cf-4972-bcc0-ec6d963c066d" }, "payment_link": { "id": "6361941e-3a81-4aa5-aaa6-60d6e052883a", "url": "https://stream-app-service.streampay.sa/api/v2/payment_links/6361941e-3a81-4aa5-aaa6-60d6e052883a" }, "metadata": { "customer_id": "cust_12345", "nested_data": { "sub_field": "sub_value", "sub_number": 678 }, "affiliate_id": "aff_987", "custom_field_1": "value_1", "custom_field_2": 12345, "campaign_source": "email_marketing" } }, "status": "SUCCEEDED", "entity_id": "e2182d3d-b4cf-4972-bcc0-ec6d963c066d", "timestamp": "2025-07-22T14:40:31.485576", "entity_url": "https://stream-app-service.streampay.sa/api/v2/payments/e2182d3d-b4cf-4972-bcc0-ec6d963c066d", "event_type": "PAYMENT_SUCCEEDED", "entity_type": "PAYMENT" } ``` ## Webhook Request Headers - `Content-Type: application/json` - `User-Agent: StreamApp-Webhook/1.0` - `X-Webhook-Event`: Event type (e.g., `PAYMENT_SUCCEEDED`) - `X-Webhook-Entity-Type`: Entity type (e.g., `PAYMENT`) - `X-Webhook-Entity-ID`: Entity UUID - `X-Webhook-Signature`: Format: `t={timestamp},v1={signature}` - `timestamp` is the UNIX timestamp when the signature was generated. - `signature` is an HMAC-SHA256 of `{timestamp}.{payload}` using your webhook’s `secret_key`. - `X-Webhook-Timestamp`: The timestamp used in the signature. ## Verifying Webhook Authenticity :::warning Always verify the `X-Webhook-Signature` before treating a webhook as authentic or updating your system (fulfillment, access, financial state). Reject requests with a missing or invalid signature. Unsigned or unverified payloads could be forged; never trust the JSON body alone. ::: To verify a webhook: 1. Extract the `X-Webhook-Signature` header and split into timestamp and signature. 2. Compute the HMAC-SHA256 of `{timestamp}.{raw_request_body}` using your webhook’s `secret_key`. 3. Compare your computed signature to the value in the header. Example (Python): ```python def verify_webhook_signature(secret: str, raw_body: bytes, signature_header: str): # signature_header: "t=TIMESTAMP,v1=SIGNATURE" parts = dict(x.split('=') for x in signature_header.split(',')) timestamp = parts['t'] signature = parts['v1'] message = f"{timestamp}.{raw_body.decode()}" computed = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest() return hmac.compare_digest(computed, signature) ``` ## Webhook Delivery & Retry Logic - Delivery Attempts: Every webhook event triggers a delivery record. Each delivery attempt and its status (pending, delivered, failed, retrying) is tracked for auditing. - Retry Schedule: If your endpoint does not respond with a 2xx status, Stream will retry delivery up to 5 times. The default retry delays (in minutes) are: 1. 1st retry: 5 minutes after the first failure 2. 2nd retry: 30 minutes after the second failure 3. 3rd retry: 2 hours (120 minutes) after the third failure 4. 4th retry: 6 hours (360 minutes) after the fourth failure 5. 5th retry: 12 hours (720 minutes) after the fifth failure After the fifth attempt, if delivery is still unsuccessful, the webhook delivery is marked as failed and no further attempts will be made. - Auditing: All delivery attempts, responses, and errors are logged and can be audited. --- ## Testing Cards ## Testing Environment In order to test your application without having to go live with cards and accounts, you can use [Moyasar's testing cards](https://docs.moyasar.com/guides/card-payments/test-cards/)) to simulate different transaction outcomes. The test cards should be used in a sandbox environment. This is also applicable if you are using the Stream's UI in sandbox mode and want to test the flow without having to use actual cards. ## Required Card Fields Below are the values that need to be entered for the non-card number fields when testing: | Field | Value | | :--- | :--- | | **Name** | Any name made of two words separated by a space (e.g., John Doe) | | **Year** | Any **future** year | | **Month** | Any month (or a future month if using the current year) | | **CVC** | Any 3 digits (4 digits for American Express (AMEX)) | --- ## List of Test Cards and Expected Outcomes Below is the list of test card numbers you can use in your test environment to simulate specific transaction statuses (Success/Failure) according to the card network. ### Mada | Card Number | Status | Message | Response Code | 3DS Note | | :--- | :--- | :--- | :--- | :--- | | `4201320111111010` | **paid** | APPROVED | 00 | ---- | | `4201320000013020` | **failed** | UNSPECIFIED FAILURE | 99 | ---- | | `4201320000311101` | **failed** | INSUFFICIENT FUNDS | 51 | ---- | | `4201320131000508` | **failed** | DECLINED: LOST CARD | 41 | ---- | | `4201321234411220` | **failed** | DECLINED | 05 | ---- | | `4201322267774310` | **failed** | DECLINED: EXPIRED CARD | 54 | ---- | | `4201326324640570` | **failed** | DECLINED: EXCEEDS WITHDRAWAL LIMIT | 61 | ---- | | `4201321144311528` | **failed** | DECLINED: STOLEN CARD | 43 | ---- | ### Visa | Card Number | Status | Message | Response Code | 3DS Note | | :--- | :--- | :--- | :--- | :--- | | `4111114005765430` | **paid** | APPROVED | 00 | Frictionless Authentication | | `4111111111111111` | **paid** | APPROVED | 00 | ---- | | `4123120000000000` | **failed** | UNSPECIFIED FAILURE | 99 | ---- | | `4123120001090000` | **failed** | INSUFFICIENT FUNDS | 51 | ---- | | `4123450131000508` | **failed** | DECLINED: LOST CARD | 41 | ---- | | `4123120001090109` | **failed** | DECLINED | 05 | ---- | | `4123128518640738` | **failed** | DECLINED: EXPIRED CARD | 54 | ---- | | `4123123033308648` | **failed** | DECLINED: EXCEEDS WITHDRAWAL LIMIT | 61 | ---- | | `4123125276780003` | **failed** | DECLINED: STOLEN CARD | 43 | ---- | | `4111118250252531` | **failed** | 3DS: attempted but not available, please ensure that you have enabled Online Purchase from your bank portal. | ---- | ECI 06 | | `4111113343111067` | **failed** | 3DS service error occurred. | ---- | 3DS fails during enrollement check | | `4111116611600661` | **failed** | The card is not enrolled in 3DS service. | ---- | ---- | | `4111112205628150` | **failed** | 3DS service error occurred. | ---- | 3DS fails during authentication attempt | | `4111115784228433` | **failed** | The authentication attempt was rejected by the issuer bank. | ---- | ---- | | `4111115620358287` | **failed** | The authentication is unavailable, please try again later or contact issuer bank if problem persisted. | ---- | ---- | ### Mastercard | Card Number | Status | Message | Response Code | 3DS Note | | :--- | :--- | :--- | :--- | :--- | | `5421080101000000` | **paid** | APPROVED | 00 | ---- | | `5105105105105100` | **failed** | UNSPECIFIED FAILURE | 99 | ---- | | `5457210001000092` | **failed** | INSUFFICIENT FUNDS | 51 | ---- | | `5204010101000000` | **failed** | DECLINED: LOST CARD | 41 | ---- | | `5204730000002514` | **failed** | DECLINED | 05 | ---- | | `5105107550274126` | **failed** | DECLINED: EXPIRED CARD | 54 | ---- | | `5105106475101067` | **failed** | DECLINED: EXCEEDS WITHDRAWAL LIMIT | 61 | ---- | | `5105107304607225` | **failed** | DECLINED: STOLEN CARD | 43 | ---- | ### American Express (AMEX) | Card Number | Status | Message | Response Code | 3DS Note | | :--- | :--- | :--- | :--- | :--- | | `340000000900000` | **paid** | APPROVED | 00 | ---- | | `371111111111114` | **failed** | UNSPECIFIED FAILURE | 99 | ---- | | `340033000000000` | **failed** | INSUFFICIENT FUNDS | 51 | ---- | | `340012340501000` | **failed** | DECLINED: LOST CARD | 41 | ---- | | `340033000000133` | **failed** | DECLINED | 05 | ---- | | `340000018441278` | **failed** | DECLINED: EXPIRED CARD | 54 | ---- | | `340000753060788` | **failed** | DECLINED: EXCEEDS WITHDRAWAL LIMIT | 61 | ---- | | `340000418501838` | **failed** | DECLINED: STOLEN CARD | 43 | ---- | --- ## Testing Apple Pay Unlike card payments where specific test card numbers are used, testing **Apple Pay** payments in the Stream's sandbox environment is based on the **amount** sent to the API (according to [Moyassar](https://docs.moyasar.com/guides/apple-pay/testing/)). A **real card** must be added to an Apple Pay Wallet to test the process, but you must be using the sandbox environment to ensure no actual payment is made. The following amounts will simulate different payment outcomes: | Amount (Minor Unit) | Amount (SAR) | Status | Message | Response Code | | :--- | :--- | :--- | :--- | :--- | | `20000` to `30000` | `200.00` to `300.00` | **paid** | APPROVED | 00 | | `100000` to `110000` | `1000.00` to `1100.00` | **failed** | UNSPECIFIED FAILURE | 99 | | `110100` to `120000` | `1101.00` to `1200.00` | **failed** | INSUFFICIENT FUNDS | 51 | | `120100` to `130000` | `1201.00` to `1300.00` | **failed** | DECLINED: LOST CARD | 41 | | `130100` to `140000` | `1301.00` to `1400.00` | **failed** | DECLINED | 05 | | `140100` to `150000` | `1401.00` to `1500.00` | **failed** | DECLINED: EXPIRED CARD | 54 | | `150100` to `160000` | `1501.00` to `1600.00` | **failed** | DECLINED: EXCEEDS WITHDRAWAL LIMIT | 61 | | `160100` to `170000` | `1601.00` to `1700.00` | **failed** | DECLINED: STOLEN CARD | 43 | > **Note:** Moyasar accepts the payment amount in the **minor currency unit** (e.g., cents), and the test environment uses this amount to return different results. --- > **Tip:** Using any other amount range than the stated above will result in the payment failing. --- ## Installments Installments allow customers to split a payment into multiple smaller payments over time. Customers can choose from available installment options (2, 3, or 4 installments) when paying an invoice or payment link. > Note: Installments are only available for one-time products. They are automatically disabled for recurring products (subscriptions). --- ## How Installments Work When a customer selects an installment option, Stream automatically calculates the amount for each installment and schedules them based on the split period. Each installment becomes a separate payment with its own due date. ![Customer selecting installment split option](/img/installment-split-selection.png) **Example:** 1,000 SAR split into 3 monthly installments: - Installment 1: 333.33 SAR (due on payment date) - Installment 2: 333.33 SAR (due 1 month later) - Installment 3: 333.34 SAR (due 2 months later) --- ## How Installment Amounts Are Calculated Installment amounts are calculated by dividing the total payment by the number of installments. If the amount doesn't divide evenly, any rounding difference is added to the last installment to ensure the total always equals the original payment amount. For example, 999.98 SAR split into 3 installments results in: 333.33 SAR, 333.33 SAR, and 333.32 SAR (the last installment is reduced by 0.01 SAR to account for the rounding difference). > Note: The total of all installments always equals the original payment amount. --- ## Configuration Configure installment settings in your [Payment Settings](https://app.streampay.sa/settings/payment-settings). Invoice and payment link settings are configured separately. ### Enable/Disable Installments #### API (Payment Links) When creating a payment link via the [Payment Links API](/api/v2-payment-links-create), you can enable or disable installments by including the `payment_methods` parameter in your request. Set `installment: true` to enable installments or `installment: false` to disable them for that specific payment link. This setting overrides the organization's default payment link installment settings. If you don't include the `payment_methods` parameter, the organization's default settings will be used. #### Settings Page You can configure the default installment settings in your [Payment Settings](https://app.streampay.sa/settings/payment-settings): - **For invoices**: Enable or disable installments in the invoice payment options (invoices can only be created from the UI) - **For payment links**: Enable or disable installments in the payment link payment options (these defaults apply when creating payment links via the UI or when the API request doesn't specify `payment_methods`) ### Split Options Configure which numbers of installments (2, 3, or 4) are available for customers to choose from. Default split options: - 2 installments, monthly - 3 installments, monthly - 4 installments, monthly The split period (the interval between installments, e.g., monthly, quarterly, yearly) cannot be changed through the API or dashboard. Contact Stream support to modify the split period. ### Minimum Amount The minimum installment amount is 100 SAR per installment. If the payment amount divided by the number of installments is less than 100 SAR, that installment option will not be available. For example, with a payment of 250 SAR: - 2 installments: 125 SAR each ✅ (available) - 3 installments: 83.33 SAR each ❌ (not available, below minimum) The minimum amount cannot be changed through the API or dashboard. Contact Stream support to modify this value. --- ## Authentication Learn how to authenticate your API requests to the Stream platform. ## Overview All API calls to Stream require authentication using your API Key Pair. Stream uses a simple but secure authentication method where you encode your credentials and include them in every request. ## Getting Your API Keys 1. Log in to your Stream dashboard 2. Navigate to [API Settings](https://app.streampay.sa/settings/api-keys) 3. Click **Create Key Pair** 4. Store your `api-key` and `api-secret` securely 5. Copy `x-api-key` to use in the APIs or you can gernerate later :::warning Important Your API secret should be kept secure and never exposed in client-side code or public repositories. Treat it like a password. ::: ## Authentication Methods Stream uses HTTP Basic Authentication with Base64 encoding of your API credentials. Encode your API key and secret as a Base64-encoded token: ```bash echo -n 'api-key:api-secret' | base64 ``` **Example:** ```bash echo -n '038cb769-9ce3-49da-9d52-17fd49bf07e8:6ea68e79-8756-4be8-9bd2-307314e8d12c' | base64 # Output: MDM4Y2I3NjktOWNlMy00OWRhLTlkNTItMTdmZDQ5YmYwN2U4OjZlYTY4ZTc5LTg3NTYtNGJlOC05YmQyLTMwNzMxNGU4ZDEyYw== ``` #### Using the Token Include the Base64-encoded token in the `x-api-key` header for all API requests: ```bash curl -X POST https://stream-app-service.streampay.sa/api/v2/payment_links \ -H "x-api-key: MDM4Y2I3NjktOWNlMy00OWRhLTlkNTItMTdmZDQ5YmYwN2U4OjZlYTY4ZTc5LTg3NTYtNGJlOC05YmQyLTMwNzMxNGU4ZDEyYw==" \ -H "Content-Type: application/json" \ -d '{ "name": "Test Payment", "amount": 100.00 }' ``` ### Using SDKs When using Stream SDKs, authentication is handled automatically. Simply provide your `x-api-key` during initialization: #### TypeScript SDK ```typescript const client = StreamSDK.init(process.env.STREAM_API_KEY!); ``` #### Express SDK ```typescript app.get( "/checkout", Checkout({ apiKey: process.env.STREAM_API_KEY!, // ... other config }) ); ``` --- ## Branches in the API API requests are always scoped to a single branch. When creating an API key, you specify which branches it can access. For each request, you can select the target branch, which determines where resources are created and accessed. ## Creating API Keys with Branch Access When creating an API key, you can specify: - Branches: List of specific branches the API key can access - Default Branch: Default branch used when `x-branch-id` header is not provided - Is All Branches: If `true`, the API key has access to all branches, present and future ## Selecting a Branch in API Requests Include the `x-branch-id` header to specify which branch to use for that request: ```bash curl -X GET https://stream-app-service.streampay.sa/api/v2/user \ -H "x-api-key: YOUR_API_KEY" \ -H "x-branch-id: branch-uuid-here" ``` If `x-branch-id` is omitted, the API key's `default_branch_id` is used. ## Branch Access Validation The API key must have access to the branch specified in `x-branch-id`: - If `is_all_branches=true`: Access to all organization branches - Otherwise: Only branches listed in `branch_ids` Requests to unauthorized branches return a 4xx error. --- ## Customer Portal (Hosted Billing) # Customer Portal (hosted billing) The **customer portal** is Stream’s hosted web app ([billing.streampay.sa](https://billing.streampay.sa)) where your **consumers** sign in and manage their relationship with **you**, within limits you configure in Stream: subscriptions (plan changes, quantity adjustment, and add‑ons allowed by your catalog), invoices, saved cards, profile and language, and payment flows that use Stream checkout. --- ## Prerequisites - **Consumer in Stream** — The person must exist as an **organization consumer** (Stream’s customer record). Create one with the [Create Consumer](/api/v2-consumers-create) API (or your existing consumer flow) and use the returned **`id`** as **`organization_consumer_id`** when creating a portal session. - **Merchant API credentials** — Your backend calls Stream with **`x-api-key`**; see [Authentication](/authentication). --- ## Send a consumer to the portal Call from your **server** only (never expose your API secret or full key pair in a browser or app) this [endpoint](/api/v2-consumer-portal-sessions-create) to get the one-time use token to open the customer portal for a specific consumer ID. **Body (minimum)** | Field | Required | Description | | ----- | -------- | ----------- | | `organization_consumer_id` | Yes | The consumer’s UUID in Stream (`id` from [Create Consumer](/api/v2-consumers-create) or [list consumers](/api/v2-consumers-list)). | **Response** On success (**HTTP 201**) the JSON includes a **`url`**. Return that **`url`** to your frontend and open it so the consumer lands on the billing portal. You can optionally include **`return_url`** (query hint for “back to merchant”; Stream does not auto-redirect), **`subscription_id`** to open a specific subscription for that consumer, or other optional fields supported by the API—see the reference for the full request schema. --- ## What subscription self-service means When your settings allow it, a customer can change an **active** subscription from the portal. Stream validates every change against your catalog and portal rules. Below is what each kind of change means in plain terms. ### Switching plans **Plan switching** is when the customer moves from one **base** subscription product to another **member of the same product switch group** you defined in Stream. Only products you placed in that group can replace one another; the portal does not allow arbitrary jumps across unrelated products. Plans with different recurring cycles (e.g MONTHLY and YEARLY) can be added in the same switch group. Typical shape of a switch: one billed **base** line is removed and another **base** line (the new plan) replaces it within the rules of that group. Whether this appears as an immediate change or is scheduled depends on billing setup and proration logic (see [Proration and timing](#proration-and-timing)). :::note Coupons Subscriptions with **coupon** locked at subscription level usually **cannot** be edited from the portal. Coupons on lines you keep can persist; lines you remove lose their coupons. ::: ### Add-ons **Add-ons** are separate catalog products attached to an active subscription—not the main plan by itself. Each add-on is only available when the subscription’s **base** catalog products make it eligible. You declare that relationship in Stream (which bases may carry which add-ons). Customers can subscribe or drop add-ons in the portal only when you enable **add / remove add-ons** for Customer Portal and the add-on mapping allows it. When creating an add-on, you get to choose a list of recurring products for which the add-on is eligible. In case a subscription has multiple recurring products and the recurring products have different eligible add-ons, the customer will be able to add **all** of these add-ons. ### Changing quantity Quantity changes behave differently depending on whether the line is part of your **switch group** (often “seat-like” bases) versus an **add-on**: | Kind of line | What “quantity” means | How you control it | | ------------ | --------------------- | ------------------ | | **Products in switch groups** | How many seats or units apply to that **base plan** row | Toggle **switch-group quantity change** in Customer Portal settings. When enabled, you can constrain **minimum** and **maximum** quantities; when disabled, customers cannot freely change those quantities in the portal. | | **Add-on lines** | How many units of that add-on are on the subscription | Toggle **add-on quantity change**. When quantity change is **off** for new add-ons, new add-ons are typically limited to quantity **one** until you widen the rule. | The portal compares changes to **allowed** quantities only; anything outside configured bounds is rejected. ### Proration and timing {#proration-and-timing} **Proration** is how billing adjusts when the subscription **mid-cycle** gains or loses value: Stream compares what was prepaid or owed for the current period against what the revised subscription implies, and settles the difference—often creating an invoice line or prompting payment **before** the next renewal. Proration does not include the current day in the new pricing. For example, if a monthly subscription (30 days) was created today and then in the same day an add-on gets added immediately through proration, the proration excludes the current day from the new bill (i.e. the user pays 29 / 30 days worth of add-on instead of 30/30.) Roughly: - **Adding add-ons**, **raising quantity**, or some **plan upgrades** commonly produce **immediate** charges (or payment steps) when money is owed, because billing value rises right away relative to what was invoiced. - **Removing add-ons**, **lowering quantity**, or changing plans in directions that reduce or reshape future billing often take effect according to billing rules. For example at **period end** or as a **scheduled** change rather than forcing an instant upfront payment in every case. - When a change crosses into a **different billing rhythm** than the subscription’s targets, moves are **often scheduled** for a future boundary rather than squeezed into an immediate payment-only flow inside the portal. From the customer’s perspective, the portal either **confirms the change outright**, **shows a payment or card step** when an adjustment is due now, or **explains that the change applies later** in the billing period—depending on the scenario above. Until any required payment clears, billing may remain on the previous shape until reconciliation finishes. Exact math and timing depend on your products’ recurrence and Stream’s reconciliation; you do **not** re-implement proration in your integration. --- ## Configure Customer Portal, branding, and catalog Open the Stream dashboard, then **[Settings → Customer Portal](https://app.streampay.sa/settings/customer-portal)**. There you tune both **permissions** customers get and **branding** of [billing.streampay.sa](https://billing.streampay.sa). ### Portal permissions (toggles) These settings mirror what you enable in Customer Portal Settings in the UI—they define what edits the hosted app even offers: | Setting | Meaning for your customers | | ------- | -------------------------- | | **Product switch** | Can move between products you grouped in **product switch groups** (plan changes within your allowed set). | | **Add / remove add-ons** | Can attach or remove add-ons you exposed through add-on eligibility. | | **Switch-group quantity change** | Can change quantity on **base** lines that belong to switch groups, within optional **min** / **max** when enabled. | | **Add-on quantity change** | Can change quantity on add-on lines, within optional **min** / **max** when enabled. | | **Subscription cancellation** | Customer-led cancellation inside portal flows is on or off. | ### Branding On the same **Customer Portal** settings area, you can personalize the hosted experience: - **Primary color** and **secondary color** — Applied across the portal UI so it feels closer to your brand. - **Favicon** — Shown in the browser tab when customers use the billing site. Uploads and color pickers persist with your organization so every session link inherits the same look. ### Catalog prerequisites (beyond the toggles) Customer Portal switches and add-ons only work if the **catalog** is set up to match: | Goal | What to set up in Stream | | ---- | ------------------------- | | Allowed plan moves | Create **product switch groups** and assign which products belong to each group. Only members of the **same** group can replace one another. | | Which add-ons exist for which bases | Maintain **add-on mappings**: for each add-on product, declare which **base** products make it eligible. | Until those mappings exist, toggles alone will not expose meaningful switches or add-ons in the portal. --- ## Summary | Role | Responsibility | | ---- | ---------------- | | **Your backend** | `POST /api/v2/consumer_portal/sessions` with `organization_consumer_id`, then return **`url`** to the client. | | **Your frontend** | Open **`url`**; do not use the merchant API key in the browser. | | **billing.streampay.sa** | Hosts the customer portal once the consumer opens **`url`. | For API key setup and authentication, see [Authentication](/authentication). --- ## Embedded Checkout Render a payment link checkout in-page with the Stream Embed SDK. Create a payment link via the API first, then pass the response **`url`** into `paymentLink`. Details on creating links (products, consumers, redirects): [Creating a Payment Link](/#creating-a-payment-link). ## Prerequisites - Same setup as payment-link creation: API key pair, and at least one product ID. See [Creating a Payment Link](/#creating-a-payment-link). - For using Apple Pay through our embedded checkouts, the following steps need to be done: - Get the text file from `https://streampay.sa/.well-known/apple-developer-merchantid-domain-association` - Host the same exact text file under your domain through this path: `.well-known/apple-developer-merchantid-domain-association` - Reach out to support team on available communication methods to register you in the PGW system. ## Example ### 1. Create a payment link `POST /api/v2/payment_links` returns an object whose **`url`** field is what you embed (same value you would redirect to). ```bash curl -s -X POST https://stream-app-service.streampay.sa/api/v2/payment_links \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Checkout", "items": [{ "product_id": "your-product-uuid-here", "quantity": 1 }], "contact_information_type": "PHONE", "currency": "SAR", "max_number_of_payments": 100 }' ``` Example response (trimmed): ```json { "url": "https://streampay.sa/ds/..." } ``` Use that `url` as `paymentLink` in the next step. Build the request body you need (customer-specific links, metadata, redirect URLs, subscriptions, etc.) using the guide and [Create payment link](https://docs.streampay.sa/api/v2-payment-links-create). ### 2. Embed checkout Load the default SDK, mount a container, call `Stream.Checkout` with the **`url`** from step 1. Inject the URL from your server—do not create payment links or expose API keys in frontend-only code. ```html ``` ## Options | Option | Type | Required | Description | | ------ | ---- | -------- | ----------- | | `paymentLink` | `string` | **Yes** | Full payment-link URL from the API (`url` field) or short ID Stream accepts | | `container` | `string` \| `Element` | **Yes** | CSS selector or DOM element for the iframe | | `minHeightPx` | `number` | No | Min iframe height (default **280**) | | `maxHeightPx` | `number` | No | Max iframe height (default **920**) | | `vhCapPx` | `number` | No | Viewport-relative height cap (default **800**) | | `vhFraction` | `number` | No | Viewport fraction for fluid height (default **0.7**) | ## See also - [Webhooks](/webhooks) — confirm payment server-side - [Public API reference](https://docs.streampay.sa/) --- ## Stream API Errors This page documents the error response format and the standardized error codes returned by Stream API endpoints. The goal is to provide a stable, predictable contract for client applications and integrators. ## Error response envelope All handled errors return a response body using the `StreamBaseResponse` envelope: ```json { "error": { "code": "STREAM_ERROR", "message": "Something wrong happened, please contact support.", "additional_info": "Optional human-readable details" } } ``` - `code`: A stable error code from `StreamErrorCodes`. - `message`: A user‑facing message associated with the code. - `additional_info`: Optional, best‑effort details to aid debugging. ## Common error classes and HTTP status codes The following exception classes map to consistent HTTP status codes: | Exception class | HTTP status | | --- | --- | | `BadRequestError` | 400 | | `UnauthorizedError` | 401 | | `ForbiddenError` | 403 | | `PermissionForbiddenError` | 403 | | `NotFoundError` | 404 | | `GoneError` | 410 | | `UnprocessableEntityError` | 422 | | `DuplicateValueError` | 422 | ## Stream error codes The following codes are used across all Stream API endpoints: | Code | Message | | --- | --- | | `STREAM_ERROR` | Something wrong happened, please contact support. | | `STREAM_UNKNOWN_ERROR` | Unknown exception happened; this exception will be handled as soon as possible. | | `PHONE_ALREADY_REGISTERED` | Phone is already registered. | | `INVALID_PARAMETERS` | Request input is not valid. | | `INVOICE_FINALISED` | Invoice is finalised and cannot undergo this action. | | `PAYMENT_REFUNDED_ALREADY` | Duplicate action; payment has already been refunded. | | `PAYMENT_REFUNDED_FAILED` | Could not refund payment; processor failed. | | `DUPLICATE_CONSUMER` | Consumer already exists. | | `DUPLICATE_PAYMENT` | A payment with the given information already exists. | | `PERMISSION_FORBIDDEN` | Permission forbidden. | | `PRODUCT_USED_IN_FINALIZED_INVOICE` | Product cannot be updated; it is used by a finalized invoice. | | `COUPON_USED_IN_FINALIZED_INVOICE` | Coupon cannot be updated; it is used by a finalized invoice. | --- ## Handling Your Own Notifications This guide explains how to handle your own organization consumer notifications by disabling default notifications and using webhooks for event-based notifications or list APIs for scheduled notifications. ## Overview By default, Stream sends notifications to your organization consumers (customers) for various events like payment confirmations, invoice updates, and subscription changes. If you want to handle these notifications yourself, you can: 1. Disable default org_consumer notifications - Contact support to enable the blocking setting 2. Subscribe to webhook events - For immediate, event-based notifications 3. Use list APIs - For scheduled notifications (reminders, overdue notices, etc.) --- ## Step 1: Disable Default Org Consumer Notifications **Important:** Before disabling default notifications, you must contact our support team to disable default notifications for your organization. This prevents all default notifications from being sent to your organization consumers. Once enabled, you can manage notifications through webhooks and list APIs as described below. --- ## Step 2: Subscribe to Webhook Events For event-based notifications (immediate actions like payment succeeded, invoice accepted, etc.), subscribe to webhook events. To create or manage your webhooks, go to [Webhook Settings](https://app.streampay.sa/settings/webhooks) and subscribe to the relevant event types. For a complete list of available webhook events, payload structure, verification, and delivery details, see the [Webhooks Guide](/webhooks). --- ## Step 3: Use List APIs for Scheduled Notifications For scheduled notifications (time-based reminders like payment due, overdue, etc.), use list APIs with appropriate filters to query for items that need notifications. For example, use the [List Payments API](/api/v2-payments-list), [List Invoices API](/api/v2-invoices-list), or [List Subscriptions API](/api/v2-subscriptions-list). --- ## Stream MCP Server MCP (Model Context Protocol) is a standard way for AI clients to call tools and read resources from external systems. The Stream MCP Server exposes Stream operations (payments, invoices, customers, products, etc.) as MCP tools so clients like Claude Desktop, Cursor, and VS Code can use them directly. **Resources vs tools:** MCP **resources** (such as the OpenAPI documentation) do not require a Stream API key. **Tools** that call the Stream API—payments, customers, invoices, products, coupons, and the rest, **do** require a valid key in your client configuration. ## Main Feature: API Documentation Integration The primary feature of this MCP server is direct integration with Stream API documentation. - It exposes the full OpenAPI spec as an MCP resource. - AI clients can read the latest API schema and stay aligned with current endpoints. - This improves tool accuracy for request fields, validation, and endpoint usage. ## Hosted endpoint Stream hosts the MCP server at: `https://mcp.streampay.sa/mcp` Configure your client to use this URL. Include `Authorization` when you use tools that hit the Stream API; you can omit it if you only read documentation resources. Use the **Connect MCP** button at the top of this page to install the server in Cursor, VS Code, or LM Studio, or to copy the command, URL, or JSON config for other clients. Paste your Stream API key into the optional field in its menu to include the `Authorization` header in the generated config; the key stays in your browser and is never sent anywhere. Without a key, only documentation resources work. --- ## Client configuration (Claude Desktop / Cursor / VS Code) Add this to your MCP config file (`claude_desktop_config.json` or `mcp.json`). Use the `headers` block whenever you rely on tools that require the Stream API (omit it for resources-only use, such as the OpenAPI spec). ```jsonc { "mcpServers": { "stream": { "url": "https://mcp.streampay.sa/mcp", "headers": { // Required for operational tools (payments, customers, etc.); omit if you only read docs resources (e.g. OpenAPI). "Authorization": "Bearer stream_api_key_here" } } } } ``` --- ## Available Tools ### Payment Links | Tool | Description | |---|---| | `create_payment_link` | Create a new checkout / payment link | | `list_payment_links` | Paginated list with optional status filter | | `get_payment_link` | Get a single payment link by ID | | `deactivate_payment_link` | Deactivate / archive a payment link | ### Customers | Tool | Description | |---|---| | `create_customer` | Create a customer with name, email, phone, metadata | | `list_customers` | Paginated list of customers | | `get_customer` | Get a single customer by ID | | `update_customer` | Update customer fields | | `delete_customer` | Soft-delete a customer | ### Products | Tool | Description | |---|---| | `create_product` | Create a one-time or recurring product | | `list_products` | List products with optional type filter | | `get_product` | Get a single product by ID | | `update_product` | Update product name, description, or price | | `archive_product` | Archive a product | ### Coupons | Tool | Description | |---|---| | `create_coupon` | Create a fixed or percentage discount coupon | | `list_coupons` | List coupons with optional status filter | | `get_coupon` | Get a single coupon by ID | | `deactivate_coupon` | Deactivate a coupon | ### Invoices | Tool | Description | |---|---| | `create_invoice` | Create a ZATCA-compliant invoice | | `list_invoices` | List invoices with filters | | `get_invoice` | Get a single invoice by ID | | `send_invoice` | (Re)send an invoice via email / SMS | | `void_invoice` | Void / cancel an unpaid invoice | ### Payments | Tool | Description | |---|---| | `list_payments` | List payments with filters | | `get_payment` | Get payment details | | `refund_payment` | Issue a full or partial refund | ### Resources Reading these resources does not require your Stream API key in the client (unlike the operational tools above). | Resource URI | Description | |---|---| | `stream://docs/openapi` | Full Stream OpenAPI spec (cached, auto-refreshed) — main documentation integration feature | --- ## Accept Consent Accept a payment consent and initiate the payment flow. Request --- ## Accept Invoice Batch Accept Invoice Batch Request --- ## Accept Scope Request Accept Scope Request Request --- ## Accounting Provider Oauth Callback OAuth redirect target called by the provider itself -- authenticated via `state` alone, not a bearer token. The caller here is the end user's browser, not an API client, so this always redirects to the frontend (`status=success|failure&provider={name}`) rather than returning JSON -- even when the exchange/verification fails. Request --- ## Activate Accounting Provider Re-activate a configured provider after verifying its stored credentials still work. Request --- ## Activate Pgw Provider Switch this organization's active payment gateway. Only ever toggles an existing PgwOrganizationConfig row — rejects if this org has no configuration for the requested provider yet (contact support to provision it). Request --- ## Activate Pricing Table Activate Pricing Table Request --- ## Activate Subscription With Payment Activate an inactive subscription by paying its latest invoice with the attached card token. Request --- ## Activities Get activity history for an invoice. Request --- ## Add Card Add a new card for the authenticated consumer. Two gateway-dependent flows share this endpoint. Moyasar (pgw_payment_id set): the frontend already ran a 1 riyal verification transaction client-side and hands back its payment id. This: 1. Fetches and validates the Moyasar payment transaction 2. Extracts card details from the payment response 3. Creates card token record with organization consumer ID 4. Voids the 1 riyal verification amount 5. Returns the new card ID MyFatoorah (pgw_payment_id omitted): there's no payment yet, so this instead creates a zero-charge MyFatoorah Verify session and returns session_id/session_expiry for the frontend to embed in MyFatoorah's session-based payment SDK. The card isn't saved yet -- completion happens later, asynchronously, via MyFatoorah's redirect, callback, or webhook (see ConsumerPortalService.complete_myfatoorah_card_tokenization). Only supported for organizations on a gateway with a native verify operation (currently MyFatoorah). Request --- ## Add Product Price Add a new price to a product in a different currency. Each product can have one active price per currency. The currency must be enabled in the organization's currency configuration. Request --- ## Approve Connection Request Approve Connection Request Request --- ## Auto Charge On Demand Charge a payment using the consumer's latest tokenized card. This endpoint allows organizations to charge a payment using the consumer's latest active tokenized card. The following conditions must be met: - The consumer must have at least one active tokenized card. - The payment must be in an unpaid status (PENDING, PROCESSING, FAILED, etc.). **This is a restricted API and is only available to some organizations.** Request --- ## Callback Handle callback from open banking payment consent flow. --- ## Cancel Cancel an invoice and optionally cancel its related subscription. Request --- ## Cancel an invoice Cancels an invoice and marks it as `CANCELED`. Use this when the invoice should no longer be collected or shown as payable. Request --- ## Cancel Metered Subscription Cancel a metered subscription from the consumer side. Request --- ## Cancel Pending Change Cancel a previously-scheduled deferred plan change for the authenticated consumer. Request --- ## Cancel Scheduled Subscription Plan Change Cancel a previously-scheduled deferred plan change. No-op (still 200) when no SCHEDULED pending change exists. If the original schedule call had already re-issued the next-cycle invoice (Arm B), the new invoice remains unchanged — it represents the upcoming cycle the consumer was notified about. Request --- ## Cancel Subscription (POST /api/v2/consumer_portal/subscription/{subscription_id}/cancel) Cancel a subscription from the consumer side. Request --- ## Cancel Subscription (POST /api/v2/subscriptions/{subscription_id}/cancel) Schedule or complete cancellation and optionally cancel related invoices. Behavior depends on subscription **status**: - **ACTIVE**: Cancellation is **scheduled for the end of the current billing period** (the subscription stays active until then; it is not moved to ``CANCELED`` immediately). - **INACTIVE**, **TRIALING**, **TRIAL_PENDING**: The subscription is **canceled immediately** (status becomes ``CANCELED``). Use ``cancel_related_invoices`` to also cancel ongoing invoices tied to this subscription. Request --- ## Change Invoice Card Change the card used for auto-renewal payments on an invoice. Request --- ## Change Metered Subscription Card Change the card used for a metered subscription. Request --- ## Change Password Change user password. Request --- ## Change Subscription Card Change the card used for auto-renewal payments on a subscription. Request --- ## Check Eligibility Check Eligibility Request --- ## Check Nafath Verification Request Status Check the status of a Nafath identity verification request. Request --- ## Check Payment Link Bnpl Eligibility Check Payment Link Bnpl Eligibility Request --- ## Check a referral code Used by the sign-up form to validate a code before the merchant can submit. Returns 404 for an unknown code and 400 for one that is no longer active. Request --- ## Consumer Profile Generate Mfa For Login Generate MFA code for consumer profile login. Request --- ## Consumer Profile Login Mfa For Access Token Verify consumer profile MFA code and receive access token. Request --- ## Consumer Update Subscription Consumer-only subscription update flow (items/quantities only). This endpoint is intentionally stricter than merchant update: - accepts only ``items`` (no description, coupons, interval, cancellation, or payment-method overrides) - rejects the update if the subscription currently has subscription-level coupons - preserves existing item-level coupons for items that stay, and drops them when the item is removed/switched out - applies add-on/switch/quantity validation via consumer_profile feature flags Execution is all-or-none: - if payment is required, neither the live subscription nor pending change is mutated yet; response is ``status=payment_required`` with ``payment_request`` and a plan preview (what is immediate after payment vs what remains scheduled) - after successful payment reconciliation (redirect/webhook), immediate + deferred targets are applied idempotently - if no payment is required, the update is applied immediately/scheduled per rules and response is ``status=update_scheduled`` with the updated subscription payload Billing-cycle rules: - same-cycle switch + add-on addition can require immediate proration for add-ons while keeping the product switch scheduled - cross-cycle switch (e.g. monthly -> yearly) with compatible target items is fully scheduled (no immediate payment) - incompatible target cycle mixes are rejected Request --- ## Create Account Create a new bank account for the organization. Request --- ## Create (POST /api/v1/invoices) Create a new invoice. Request --- ## Create (POST /api/v2/api-keys) Create a new API key for programmatic access. Request --- ## Create (POST /api/v2/payments) Create a new payment for an invoice. Request --- ## Create (POST /api/v2/public/waitlist) Add a user to the waitlist. Request --- ## Create (POST /api/v2/support) Create a new support ticket. Request --- ## Create (POST /api/v2/webhooks) Create a new webhook endpoint to receive event notifications. Request --- ## Create Branch Create a new branch for the organization. If a branch with the same name already exists, returns the existing branch. Request --- ## Create Consumer (POST /api/v1/consumers) Create a new consumer with contact information and payment details. Request --- ## Create Consumer (POST /api/v2/consumers) Create a new consumer with contact information and payment details. Request --- ## Create Coupon Create a new coupon with discount details and active/inactive status. Request --- ## Create Discount Rule Create Discount Rule Request --- ## Create Form (POST /api/v2/forms) Create Form Request --- ## Create Form (POST /api/v2/payment_link_forms) Create Form Request --- ## Create Installment Rule Create Installment Rule Request --- ## Create Invite For A User To Join The Organization By Email Send an email invitation for a user to join the organization. An invitation email will be sent to the specified address with a link to accept the invitation. Organization admins can invite users to any branch, while branch admins can only invite to their own branches. Request --- ## Create Invoice Create a new invoice with items and payment details. Important: For most use cases, use the Checkout/Payment Link API (`/v2/payment_links`) instead. It generates a payment link that you can embed in your own web page or redirect to. Use Cases: - Create an invoice and manually mark it as paid for reference - Automated invoice generation Request --- ## Create Metered Subscription Create a new metered subscription linking a METERED product to an organization consumer. The subscription is active immediately when its start time is reached. Usage can begin as soon as the subscription exists, and billing will use the subscription's billable window. Request --- ## Create Organization Create a new organization and associate it with the current user. Request --- ## Create Payment Intent Create a payment intent before calling Moyasar. The frontend calls this endpoint first to create an intent and get an intent_id. This intent_id is then passed to Moyasar in the metadata. When webhook/redirect happens, we extract payment_ids from the intent. Requested installment settings are validated here and stored on the intent. Validations: - All payment IDs exist - All payments belong to the same invoice - All payments are unpaid (PENDING, PROCESSING, FAILED, FAILED_INITIATION) Args: request: HTTP request (required by rate limiter) payment_intent_request: Request containing payment_ids payment_intent_service: Injected service instance Returns: PaymentIntentResponse with intent_id and payment_ids Request --- ## Create Payment Link Create a new checkout/payment link for collecting payments. - **One-time products**: Creates an invoice (with its associated payment) when paid. - **Recurring products**: Creates a subscription (with its associated invoice and payment) when paid. **Notes:** - Cannot mix one-time and recurring products in the same payment link. - Installments are automatically disabled for recurring products (subscriptions). - Invoices, subscriptions and payments are created only after a successful payment. Request --- ## Create Payment Link Form Submission Create a DRAFT form submission for a payment link so the payer can resume via submission id. Request --- ## Create Payment Link Intent Create a payment link intent before calling payment gateway. Request --- ## Create a consumer portal session Creates a short-lived, single-use portal session token for a merchant-owned consumer. The returned URL can be used to redirect the consumer or embed the portal in an iframe. Request --- ## Create Pricing Table Create an embeddable pricing table backed by existing products + prices. Request --- ## Create Pricing Table Checkout Create a checkout session backed by a one-shot Moyasar PaymentLink. On success returns the consumer-portal `checkout_url`. If the resolved consumer already has an active subscription and the table's `subscription_collision_policy` is `BLOCK_WITH_MANAGE_URL`, returns 409 with `code: already_subscribed` and a `manage_url` (when available). Request --- ## Create Pricing Table Customer Session Issue a short-lived signed `client_secret` for the embed. The frontend SDK passes the returned token as `customer-session-client-secret` on `` to enable viewer-aware features (current plan, upgrade/downgrade hints, manage URL, "block if already subscribed"). Request --- ## Create Product Create a new product with pricing and description. Request --- ## Create Product Switch Group Create a product switch group and optional initial product memberships. Request --- ## Create Quantity Change Group Create Quantity Change Group Request --- ## Create Scope Request 201 for a new request; 200 with the pending one, unchanged, when there already is one. Request --- ## Create Scope Request From App The assistant asks for write access on the connection it is calling under. The one route in this package for a delegated caller. It goes through the general ``get_current_user`` so that the delegated resolver runs with all of its gates (live grant, admission, allow-list); the service then refuses any caller that did not arrive as a delegated one. No permission is checked, so a read-only connection can ask. Request --- ## Create Subscription Create a new subscription with items, coupons, and payment methods. Important: For most use cases, use the Checkout/Payment Link API (`/v2/payment_links`) instead. It generates a payment link that you can embed in your own web page or redirect to. Use Cases: - Create a subscription and manually marking it as paid for reference - Automated subscription creation Request --- ## Data Entry Submit confirmed business/owner data (data entry step). Request --- ## Deactivate Accounting Provider Deactivate a configured provider. Stream will never push to it while inactive. Request --- ## Deactivate Metered Subscription Deactivate a metered subscription by setting its end time to now. This subscription stops being billable after this exact time. Note: deactivated subscriptions can still have usage recorded; it won't be billed. Request --- ## Deactivate Pricing Table Deactivate Pricing Table Request --- ## Delete Account Delete a bank account from the organization. Request --- ## Delete Delete a webhook endpoint. Request --- ## Delete Branch Delete a branch from the organization. Request --- ## Delete By Api Key Revoke an API key. Organization admins can revoke any API key in the organization. Regular users can only revoke API keys they created. Request --- ## Delete Connection Delete Connection Request --- ## Delete Consumer (DELETE /api/v1/consumers/{org_consumer_id}) Delete a consumer from the organization. Request --- ## Delete Consumer (DELETE /api/v2/consumers/{consumer_id}) Delete a consumer from the organization. Request --- ## Delete Consumer Card Delete a card if it's not associated with active subscriptions or ongoing installment invoices. This performs a soft delete by setting is_deleted=True and deleted_at timestamp. Request --- ## Delete Coupon Delete a coupon from the organization. Request --- ## Delete Discount Rule Delete Discount Rule Request --- ## Delete Form (DELETE /api/v2/forms/{form_id}) Delete Form Request --- ## Delete Form (DELETE /api/v2/payment_link_forms/{form_id}) Delete Form Request --- ## Delete Installment Rule Delete Installment Rule Request --- ## Delete Invoices Delete all invoices (non-production environments only). --- ## Delete Payment Delete a payment record. Request --- ## Delete Payment Link Document Delete a payment link document (attached or not yet attached). Request --- ## Delete Pricing Table Delete Pricing Table Request --- ## Delete Product Delete a product from the organization. Request --- ## Delete Product Price Archive (soft delete) a product price. Cannot delete the last active price for a product. Each product must have at least one active price. Request --- ## Delete Product Switch Group Soft-delete a product switch group and remove product memberships. Request --- ## Delete Quantity Change Group Delete Quantity Change Group Request --- ## Delete Subscription Freeze Remove a freeze period from a subscription. Deletes the specified freeze period, allowing the subscription to resume normal invoice generation immediately (if the freeze period was active). Request --- ## Delete Subscriptions Delete all subscriptions (non-production environments only). --- ## Document Upload Submit business documents for onboarding (document upload step). Request --- ## Document Upload Extraction Status Poll the async extraction job queued by the last document-upload call. Returns None if no job was ever queued, or it has expired, a PENDING/PROCESSING status while the extraction is still running, or SUCCESS/FAILED once it's done. --- ## Esign Webhook Handle e-signature webhook notifications for envelope status updates. Request --- ## Exchange a portal session token for a consumer access token The portal frontend calls this endpoint to exchange a short-lived, single-use session token for a standard consumer JWT. The token is invalidated after first use. Request --- ## Export Task Create an export task to generate reports in various formats. Request --- ## Export Task Status Get the status and download link of an export task. Request --- ## Forgot Password Initiate password reset process by sending verification code to user email. Request --- ## Freeze Subscription Freeze a subscription to pause invoice generation for a specific period. During the freeze period, no invoices will be generated for the subscription. The subscription will automatically resume after the freeze period ends. Request --- ## Generate An Mfa For Verifying An Identifier Generate a multi-factor authentication code for identity verification. Request --- ## Generate Mfa For Login Generate and send multi-factor authentication code for login. Request --- ## Generate Mfa For Phone Verification Generate and send multi-factor authentication code for phone verification. Request --- ## Generate Nafath Verification Request Initiate Nafath identity verification request. Request --- ## Get Accounting Provider Branch Mappings Read back this organization's saved Stream-branch -> platform-branch mappings. Request --- ## Get Accounting Provider Config Read back this organization's stored config field values. Never includes credentials/tokens. Request --- ## Get Accounting Provider Metadata Describe what fields an accounting provider needs, driving the frontend's setup form. Request --- ## Get All Accounts List all bank accounts configured for the organization. --- ## Get All (GET /api/v1/invoices) List all invoices with pagination, filtering, and sorting. Request --- ## Get All (GET /api/v2/api-keys) List all API keys for the organization. Organization admins see all API keys. Regular users see only API keys they created. --- ## Get All (GET /api/v2/bank-transfer) List all bank transfer settlements with pagination, filtering, and sorting. Request --- ## Get All (GET /api/v2/webhooks) List all webhooks configured for the organization. --- ## Get All Branches List all branches for the organization. Returns branches based on user role: - Organization admins see all branches in the organization - Branch admins and branch users see only branches they're assigned to --- ## Get All Consumers (GET /api/v1/consumers) List all consumers with filtering options. Request --- ## Get All Consumers (GET /api/v2/consumers) List consumers with pagination, sorting, and optional filters. **Query filters** (all optional; combine as needed): - `search_term` — partial match on name, email, phone, alias, external_id, associated entity name, or associated entity field values **External metadata filters** (optional, combine with other filters): - Use any external metadata key you stored on create/update as a query parameter: `external_metadata.=` - Exact match, case-sensitive. Multiple keys are ANDed. - Example: if a consumer was created with `"external_metadata": {"segment": "enterprise", "tier": "gold"}`, filter with `?external_metadata.segment=enterprise&external_metadata.tier=gold` **Pagination**: `page` (default 1), `limit` (default 10, max 100) **Sorting**: `sort_field` (`name`, `created_at`, `email`, `phone_number`, `external_id`; default `created_at`), `sort_direction` (`asc` or `desc`; default `desc`) **Example**: `/api/v2/consumers?search_term=Acme&external_metadata.segment=enterprise&page=1&limit=20` Request --- ## Get All Invoices List all invoices for the authenticated consumer with pagination, filtering, and sorting. Request --- ## Get All Organization Users List all users who are members of the organization. --- ## Get All Subscriptions List all subscriptions for the authenticated consumer with pagination, filtering, and sorting. Request --- ## Get (GET /api/v1/invoices/{invoice_id}) Get invoice by ID. Request --- ## Get (GET /api/v2/short-url/{short_url_id}) Redirect from short URL to the original destination URL. Request --- ## Get (GET /api/v2/webhooks/{webhook_id}) Retrieve detailed information about a specific webhook by its ID. Request --- ## Get Available Events List all available webhook events that can be subscribed to. --- ## Get Bnpl Order Status Get Bnpl Order Status Request --- ## Get Branch By Id Retrieve detailed information about a specific branch by its ID. Request --- ## Get By Id Retrieve detailed information about a specific bank account by its ID. Request --- ## Get Connected Apps Authorization Server Metadata Get Connected Apps Authorization Server Metadata --- ## Get Connected Apps Key Set Get Connected Apps Key Set --- ## Get Connection Request Get Connection Request Request --- ## Get Consent Payment Pdf Status Return payment invoice PDF status for a consent; starts generation when not completed. Request --- ## Get Consumer (GET /api/v1/consumers/{consumer_id}) Retrieve detailed information about a specific consumer by their ID. Request --- ## Get Consumer (GET /api/v2/consumers/{consumer_id}) Retrieve detailed information about a specific consumer by their ID. Request --- ## Get Consumer Card List Get a list of cards for a consumer. --- ## Get Consumer Insights Get analytics and insights for a specific consumer. Request --- ## Get Consumer Me Get profile information for the currently logged-in consumer. --- ## Get Consumer Portal Config Get public consumer portal configuration by lookup ID. Request --- ## Get Conversion Rates Get conversion rates payload for the frontend consumer portal. --- ## Get Coupon Retrieve detailed information about a specific coupon including redemption history. Request --- ## Get Deliveries Get all deliveries for a webhook. Limited to latest 100 deliveries only. Request --- ## Get Delivery By Id Get a webhook delivery by ID. Request --- ## Get Details (GET /api/v1/invoices/{invoice_id}/details) Get detailed information about an invoice. Request --- ## Get Details (GET /api/v2/consumer_portal/consent/{consent_id}/details) Get detailed information about a payment consent including invoice and payment options. Request --- ## Get Discount Rule Get Discount Rule Request --- ## Get Email Template Configuration Return the organization's notification email template configuration. --- ## Get Form (GET /api/v2/forms/{form_id}) Get Form Request --- ## Get Form (GET /api/v2/payment_link_forms/{form_id}) Get Form Request --- ## Get Form Submission (GET /api/v2/consumer_portal/form-submissions/{submission_id}) Resume a form submission (answers + uploaded documents with short-lived URLs). Request --- ## Get Form Submission (GET /api/v2/forms/{form_id}/submissions/{submission_id}) Get Form Submission Request --- ## Get Form Submission (GET /api/v2/payment_link_forms/{form_id}/submissions/{submission_id}) Get Form Submission Request --- ## Get Installment Rule Get Installment Rule Request --- ## Get Invoice Retrieve detailed information about a specific invoice by its ID. Request --- ## Get Invoice Batch Example Csv Get Invoice Batch Example Csv --- ## Get Invoice Consent Url Get the payment consent URL for an invoice. Request --- ## Get Invoice Details Get detailed information about an invoice for the authenticated consumer. Request --- ## Get Metered Subscription Retrieve a metered subscription. Request --- ## Get Metered Subscription Details Get details for a consumer's metered subscription. Request --- ## Get Org All Payments List all payments across the organization with pagination, filtering, and sorting. Note: When `view` is provided, filter parameters (statuses, from_date, to_date) are ignored and determined by the view, but search_term is preserved. Request --- ## Get Organization Accounting Posting Behavior Get the organization's accounting posting settings. --- ## Get Organization Address Get the organization's national address; fields are null until it is set. --- ## Get Organization Refund Policy Get the latest refund policy for the current organization. --- ## Get Own Connection A connected app reads the connection its credential belongs to, branches included. For a connected app only, with either credential. Like the assistant's scope-request route it goes through ``get_current_user`` so the resolver runs with all of its gates, and the service refuses any caller that did not arrive as a connected app. No permission is checked, so it works whatever the connection's scopes are. --- ## Get Payment Retrieve detailed information about a specific payment by its ID. Request --- ## Get Payment Gateway Activities List distinct payment gateway statuses for a payment, newest first. Duplicate reports of the same status for one gateway payment are collapsed into one activity. Request --- ## Get Payment Link Retrieve detailed information about a specific payment link by its ID. Request --- ## Get Payment Link Details Get detailed information about a payment link. Request --- ## Get Payment Link Insights Get analytics and insights for a specific payment link. Request --- ## Get Payment Request Get payment request configuration for consumer with amount 1 and save_card=true. This endpoint returns a payment request object similar to invoice payment requests but with fixed amount of 1 and save_card always true for both credit card and Apple Pay. --- ## Get Pricing Table Get Pricing Table Request --- ## Get Product Retrieve detailed information about a specific product by its ID. Request --- ## Get Product Insights Get analytics and insights for a specific product. Request --- ## Get Product Switch Group Get a single product switch group with product IDs. Request --- ## Get Public Pricing Table Get Public Pricing Table Request --- ## Get Public Pricing Table With Viewer Same as GET / but `Authorization: Bearer ` is required. Request --- ## Get Related Companies Get all companies/organizations that the user belongs to. --- ## Get Subscribers By Status Get list of subscribers filtered by their subscription status. Request --- ## Get Subscription Retrieve detailed information about a specific subscription by its ID. Request --- ## Get Subscription Details Get detailed information about a subscription for the authenticated consumer. Request --- ## Get Subscription Insights Get analytics and insights for a specific subscription. Request --- ## Get Template Config Get invoice template configuration. --- ## Get Top Used Products Return the most frequently used products. Uses the same filters as list products, plus `n`. **Query filters** (all optional; combine as needed): - `search_term` — partial match on product name or description - `active` — `true` for active products only, `false` for inactive only - `type` — product type: `RECURRING`, `ONE_OFF`, or `METERED` (when enabled) - `currency` — only products with an active price in this ISO currency code (e.g. `SAR`) - `n` — number of results to return (1-10, default 9) **External metadata filters** (optional, combine with other filters): - Use any external metadata key you stored on create/update as a query parameter: `external_metadata.=` - Exact match, case-sensitive. Multiple keys are ANDed. - Example: if a consumer was created with `"external_metadata": {"segment": "enterprise", "tier": "gold"}`, filter with `?external_metadata.segment=enterprise&external_metadata.tier=gold` **Sorting**: `sort_field` (`price`, `name`, `created_at`; default `created_at`), `sort_direction` (`asc` or `desc`; default `desc`) **Example**: `/api/v2/products/top-used?n=5&active=true&external_metadata.plan=pro` Request --- ## Get Total Settlement Sum Get the total sum of all settlements based on filters. Request --- ## Get Total Unsettled Sum Get the total sum of all unsettled transfers based on filters. Request --- ## Get User And Organization Info Retrieve information about the authenticated user and their organization. It requires a valid authentication token and can be used to verify API connectivity and retrieve user context. This endpoint returns details about the currently authenticated user and their associated organization. --- ## Get User Get current user profile information. --- ## Get User Roles Permission Get all permissions granted to the user based on their roles. --- ## Get Zendesk Token Get a Zendesk token for the authenticated user. --- ## Gmv Set the past GMV for organization before Stream. Request --- ## Health check endpoint Check the health status of MongoDB, PostgreSQL, and Redis connections --- ## Iban Verification Confirm IBAN and beneficiary name (IBAN verification step). Request --- ## Initiate Initiate an invoice payment consent flow for open banking. Request --- ## Invoice Is Subscription Cancellable Check if the subscription related to an invoice can be canceled. Returns: - `is_subscription_cancellable=True`: The invoice is associated with a subscription AND that subscription is in a cancelable state. - `is_subscription_cancellable=False`: Either the invoice has no associated subscription, or the subscription is not in a cancelable state. Request --- ## Issue Connected App Token The token endpoint: code, refresh and, for a service acting as itself, token exchange. The form is read off the request rather than declared, so that a missing or repeated parameter is answered by Authlib in the OAuth error shape and not by a 422. --- ## Kyb Documents Kyb Documents Request --- ## Kyb Information Get the current onboarding state for the organization. --- ## Kyb Profile Kyb Profile Request --- ## Kyb Review Result Receive the KYB review outcome from zendesk. Request --- ## List Accounting Provider Branch Options List this provider's branches/locations available to map Stream branches onto. Raises ACCOUNTING_BRANCHES_NOT_SUPPORTED if this provider/org has no branches/locations concept -- the frontend should catch that code and skip branch mapping entirely. Request --- ## List Accounting Providers List every known accounting provider and this organization's configuration status for each. --- ## List Add Ons List add-on eligibility mappings for the organization. --- ## List An Invoice'S Accounting Sync Attempts The sync attempts of an invoice, newest first, with the provider request/response of each. Request --- ## List Connection Activity List Connection Activity Request --- ## List Connections List Connections Request --- ## List Coupons List all coupons with pagination, filtering, and sorting options. Request --- ## List Currencies Get all active currencies. --- ## List Discount Rules List Discount Rules --- ## List Form Submissions (GET /api/v2/forms/{form_id}/submissions) List Form Submissions Request --- ## List Form Submissions (GET /api/v2/payment_link_forms/{form_id}/submissions) List Form Submissions Request --- ## List Form Submissions For Consumer List Form Submissions For Consumer Request --- ## List Forms (GET /api/v2/forms) List Forms Request --- ## List Forms (GET /api/v2/payment_link_forms) List Forms Request --- ## List Installment Rules List Installment Rules Request --- ## List Invoice Batch Rows List Invoice Batch Rows Request --- ## List Invoice Batches List Invoice Batches Request --- ## List Invoices Accounting Sync Status Invoices with their accounting sync status and, inline, the records that status is made of. Request --- ## List Invoices List all invoices with pagination, filtering, and sorting options. Request --- ## List Metered Subscriptions List metered subscriptions. Request --- ## List Organization Config List Organization Config --- ## List Override Installment Rules OVERRIDE rules a bill collector can pick as an installment override when creating an invoice or payment link. Pass the invoice or link ``currency`` to list only rules that can be assigned to it. Request --- ## List Payment Links List all payment links with pagination, filtering, and sorting options. Request --- ## List Payments List all payments with pagination, filtering, and sorting options. Payments can be filtered by invoice IDs, statuses, date ranges, or search terms. Request --- ## List Pgw Providers List available payment gateways and which one is active for this organization. --- ## List Pricing Tables List Pricing Tables Request --- ## List Product Switch Groups List product switch groups for the organization. Request --- ## List Products List products with pagination, sorting, and optional filters. **Query filters** (all optional; combine as needed): - `search_term` — partial match on product name or description - `active` — `true` for active products only, `false` for inactive only - `type` — product type: `RECURRING`, `ONE_OFF`, or `METERED` (when enabled) - `currency` — only products with an active price in this ISO currency code (e.g. `SAR`) **External metadata filters** (optional, combine with other filters): - Use any external metadata key you stored on create/update as a query parameter: `external_metadata.=` - Exact match, case-sensitive. Multiple keys are ANDed. - Example: if a consumer was created with `"external_metadata": {"segment": "enterprise", "tier": "gold"}`, filter with `?external_metadata.segment=enterprise&external_metadata.tier=gold` **Pagination**: `page` (default 1), `limit` (default 10, max 100) **Sorting**: `sort_field` (`price`, `name`, `created_at`; default `created_at`), `sort_direction` (`asc` or `desc`; default `desc`) **Example**: `/api/v2/products?active=true&type=RECURRING&external_metadata.category=saas¤cy=SAR` Request --- ## List Providers List Providers --- ## List Quantity Change Groups List Quantity Change Groups --- ## List Resource Field Definitions List Resource Field Definitions Request --- ## List Scope Requests List Scope Requests Request --- ## List Settlements List Settlements Request --- ## List Subscription Freezes List all freeze periods for a specific subscription. Request --- ## List Subscriptions List all subscriptions with pagination, filtering, and sorting options. Request --- ## List Switchable Subscription Products List per-line switch options for every item on the consumer's subscription. Each entry in ``items`` corresponds to a current subscription line; lines whose product is not part of any switch group come back with an empty ``switchable_products`` so the UI can render the full subscription consistently. Request --- ## List User Organizations List organizations the authenticated user is a member of. --- ## Liveness probe endpoint Process-alive check with no dependency calls; used by the k8s livenessProbe so transient DB/Mongo/Redis slowness can't restart an otherwise-healthy pod. --- ## Login For Access Token Authenticate user with email and password and return access tokens. Request --- ## Login Mfa For Access Token Verify MFA code and complete login to receive access tokens. Request --- ## Logo Upload Upload and set the organization logo. Request --- ## Lower Connection Lower Connection Request --- ## Mark Account As Default Set a bank account as the default account for the organization. Request --- ## Mark Failed Reject a bank transfer payment and mark it as failed. Request --- ## Mark Payment As Paid Manually mark a payment as paid (full or partial). Record a payment received through manual methods (CASH, BANK_TRANSFER, CARD, or QURRAH). **Full:** omit ``amount`` or pass ``amount == payment.amount``. **Partial:** pass ``amount < payment.amount`` with remainder greater than 1 in the payment currency (not allowed while UNDER_REVIEW). - Selected payment amount is reduced and marked SUCCEEDED. - A new PENDING ``SPLIT_REMAINDER`` payment is created for the remainder. - ``invoice.type`` is unchanged (ONE_OFF stays ONE_OFF). - Invoice completes only when every payment is paid. Request --- ## Moyasar Payment Webhook Receive and process payment webhooks from Moyasar payment gateway. Request --- ## Moyasar Redirect Handle redirect from Moyasar payment gateway after payment completion. Request --- ## Myfatoorah Payment Webhook Receive and process server-to-server webhooks from the MyFatoorah payment gateway. Request --- ## Myfatoorah Settlement Webhook Receive MyFatoorah settlement (BALANCE_TRANSFERRED) webhooks, resolving the organization from the payload. Request --- ## Myfatoorah Payment Callback Receive and process payment callbacks from the MyFatoorah payment gateway. Request --- ## Myfatoorah Redirect Handle redirect from MyFatoorah payment gateway after payment completion. Reuses the exact same generic redirection logic as moyasar_redirect — the org's active PGW adapter (resolved from organization_id) determines whether the payment is verified via Moyasar's API or MyFatoorah's decrypt/GetPaymentDetails. Request --- ## Oauth Authorize Redirect the browser straight to the OAuth provider's consent screen. No recaptcha here -- this is a plain browser navigation (e.g. a link/button), which can't carry a token. The provider's own consent screen plus the callback's state check are the abuse guards for this leg. Request --- ## Oauth Callback Handle the provider's redirect back, then always hand the browser to the frontend. Never returns an error response to the browser -- handle_oauth_callback never raises, so every outcome becomes a `status=...` query param on the redirect. That includes the user denying consent: Google then redirects here with `error=access_denied` and no `code` at all, so `code` is an optional query param rather than a required one (a required, missing `code` would otherwise fail FastAPI's own request validation before this handler runs, surfacing a raw JSON 422 instead of the frontend redirect every other failure gets). `state` is optional for the same reason -- Google itself always echoes it back, but a malformed or bot-issued hit on this URL might not carry one at all. Request --- ## Oauth Complete Signup Finish a brand-new OAuth signup: fill in the profile fields and become VERIFIED. `pending_signup_id`/`verification_code` (from the callback's redirect) are this request's only auth -- same trust model as the existing GET /user/verify email-link flow. Request --- ## Organization Collection Stats Get collection statistics grouped by time frame. Request --- ## Organization Revenue Get organization revenue analytics with time-based filtering. Request --- ## Organization'S Invoiced Amount In Time Range Get total invoiced amount statistics with comparison to previous period. Request --- ## Organization'S Latest Invoices Get the most recent invoices for the organization. --- ## Organization'S Outstanding Amount In Time Range Get unpaid invoice amount statistics with comparison to previous period. Request --- ## Organization'S Total Overdue Amount Get total overdue invoice amount. --- ## Organization'S Total Paid Amount (And Breakdown By Payment Method) In Time Range Get total paid amount with breakdown by payment method. Request --- ## Paid Amount Metrics Get paid amount metrics over time for charting. Request --- ## Pause Schedulers Pause all Temporal core schedules so background cron does not fight E2E runs. Does not cancel in-flight workflows. Manual ``POST /v2/schedulers/...`` triggers still work for intentional suite sweeps. Request --- ## Pay By Qurrah Submit a Qurrah payment with optional receipt file upload. Request --- ## Pay Subscription Update Intent With Attached Card Pay a pending subscription update intent using the subscription's attached card. Request --- ## Plan Choice Select billing plan and submit for review (plan choice step). Request --- ## Preferred Language Set the preferred language for a consumer. Request --- ## Preview Automatic Discount What the invoice screen shows before the invoice exists. Returns the coupon (if any) an active discount rule suggests for this beneficiary/product in this currency. Nothing is applied automatically: the invoice-creation flow uses this same resolution (with the invoice's actual currency) only to reject the invoice if the suggested coupon isn't included, so the caller must fetch this first and pass it explicitly when creating the invoice. `currency` is required so the suggestion always matches what invoice creation will actually enforce -- a coupon fixed to a currency the invoice isn't in is never "applicable", the same as an inactive or otherwise ineligible one. Request --- ## Preview Installment Payment Calendar Payment calendar shown while raising an invoice or configuring a payment link. Request --- ## Preview Invoice Batch Preview Invoice Batch Request --- ## Preview Payment Link Installment Options Preview installment options for a payment link after quantity changes. Recomputes the cart total from item_quantities (same pricing path as intent create) and returns installment options that apply to that total. Does not create an intent. Request --- ## Print Invoice Return download link for a completed consent invoice PDF. Request --- ## Print Payment Invoice Get download link for invoice PDF with payment details. Request --- ## Print Payment Link Pgw Payment Invoice Return download link for a payment-link invoice PDF. Request --- ## Print Proforma Invoice Pdf Return proforma invoice PDF status and URL; creates a pending row and starts generation when missing. Request --- ## Print Vat Invoice Get download link for VAT-compliant invoice PDF. Request --- ## Profile Completion (GET /api/v2/organization/profile-completion) Get the remaining complete profile steps for the current organization. --- ## Profile Completion (PUT /api/v2/organization/profile-completion) Set one of the specified steps as completed. Request --- ## Record Metered Usage Record metered usage events in bulk Request --- ## Refresh Access Token Refresh access token using refresh token from cookies. --- ## Refund Payment Process a refund for a payment. Refunds settle the requested amount or the remaining refundable balance for the selected payment. If that payment is part of a shared Moyasar charge, set ``payment_is_part_of_related_set`` to true to distribute the refund across related payments in invoice order. Request --- ## Register A Bank Transfer For An Organization Register a bank transfer from external banking systems via webhook. Request --- ## Register Connected App Client Register Connected App Client --- ## Register Invited User Accept an organization invite. Existing users send invite_id + Bearer token; new users send profile fields. Request --- ## Register Pending User Register a new user and send email verification code. Request --- ## Register User :::caution[deprecated] This endpoint has been deprecated and may be replaced or removed in future versions of the API. ::: Register a new user account (deprecated, use /user/register instead). Request --- ## Reject Connection Request Reject Connection Request Request --- ## Reject Scope Request Reject Scope Request Request --- ## Renew Zatca Production Csid Renew Zatca Production Csid Request --- ## Reopen Form Submission Convert SUBMITTED back to DRAFT when the payer goes back to edit before paying. Request --- ## Replay Webhook Delivery Replay a webhook delivery. Request --- ## Reschedule Payment Set a payment's `scheduled_on` without the posted-invoice validation. Request --- ## Resend Verify Email Pending User Resend email verification code to a pending user. Request --- ## Reset Password Set new password using verified reset token. Request --- ## Resolve Accounting Provider Post-Auth Field Values Resolve dependent POST_AUTH fields tier-by-tier (e.g. Zoho's org id -> payment account). Submit known values, get back everything resolved so far this session plus which fields are still pending an unmet dependency -- call again with newly-picked values to unlock the next tier. Purely a staging/resolution aid; never writes to the persisted config. Request --- ## Resume Schedulers Unpause all Temporal core schedules after E2E cleanup. Request --- ## Revoke Connected App Token Revoke Connected App Token --- ## Set Connection All Branches Turn a connection's reach to every branch of the organization on or off. Organization admin only. Request --- ## Set Payment Flow Set the payment flow configuration for a consent. Request --- ## Set Subscription Period Set a subscription's current period so renewal or cycle-push sweeps see it as due. Request --- ## Settlement Sum Metrics Get total settlement sum statistics with comparison to previous period. Request --- ## Skip Getting Started Skip the getting-started screen. --- ## Start Accounting Provider Oauth Store pre-auth field values and return the provider's OAuth authorization URL. Request --- ## Start Bnpl Checkout Start Bnpl Checkout Request --- ## Start Connected App Authorization Start sign-in in the merchant's browser. A request whose client or redirect URI cannot be trusted gets a page, never a redirect: the only address available to redirect to is the one that failed validation. --- ## Start Zatca Onboarding Start Zatca Onboarding Request --- ## Store And Send Contact Us Email Submit a contact us form and send notification email. Request --- ## Submit Accounting Provider Credentials Submit non-OAuth credentials (API key or username/password) for a provider and verify them. Request --- ## Submit Accounting Provider Post-Auth Config Submit post-auth field values (e.g. which chart-of-accounts entry payments should post to). Request --- ## Submit Mfa For Phone Verification Generate and send multi-factor authentication code for phone verification. Request --- ## Subscribers Metrics Get subscriber count metrics over time for charting. Request --- ## Subscription Are Invoices Cancellable Check if a subscription has any cancelable ongoing invoices. Returns: - `are_invoices_cancellable=True`: The subscription has one or more ongoing invoices that are cancellable. - `are_invoices_cancellable=False`: The subscription has no ongoing invoices (all invoices are completed, canceled, or no invoices exist). Request --- ## Switch Branch Switch the active branch for the current user session. Request --- ## Switch Organization Switch the active organization for the current user session. Request --- ## Sync Invoices To The Accounting Platform Sync the unsynced / failed records of these invoices (at most one page worth), in order. Request --- ## Uncancel Subscription (PUT /api/v2/consumer_portal/profile/subscription/{subscription_id}/uncancel) Withdraw a pending cancellation so the subscription continues after the current billing period. If the subscription is not scheduled to be cancelled, succeeds as a no-op. Request --- ## Uncancel Subscription (PUT /api/v2/subscriptions/{subscription_id}/uncancel) Withdraw a pending cancellation so the subscription continues after the current billing period. Only **ACTIVE** subscriptions are accepted. If the subscription is not scheduled to be cancelled, the request succeeds as a no-op. Request --- ## Unfreeze Subscription Unfreeze a subscription for the logged-in organization consumer. Request --- ## Unifonic Sms Webhook Endpoint Handle SMS notification status webhooks from Unifonic. Request --- ## Unifonic Whatsapp Webhook Endpoint Handle WhatsApp webhooks from Unifonic. For message.received events: triggers invoice auto-response flow. For other event types (message.sent, message.delivered, etc.): handles notification status updates. Request --- ## Update Account Update bank account details. Request --- ## Update Accounting Provider Branch Mappings Replace the saved branch mappings with the given full set. Omitted branches are unmapped. Request --- ## Update Accounting Provider Config Update existing config field values. Never accepts credentials/tokens through this endpoint. Request --- ## Update Add Ons Overwrite eligible products for a single add-on product. Request --- ## Update (PUT /api/v1/invoices/{invoice_id}) Update an existing invoice. Request --- ## Update (PUT /api/v2/public/waitlist/{waitlist_id}) Update waitlist entry information. Request --- ## Update (PUT /api/v2/webhooks/{webhook_id}) Update webhook configuration such as URL or event types. Request --- ## Update Branch Update branch information. If a branch with the same name already exists, returns an error. Request --- ## Update Cards Settings Update credit card payment settings for the organization. Request --- ## Update Consumer (PUT /api/v1/consumers/{org_consumer_id}) Update consumer information such as contact details or payment methods. Request --- ## Update Consumer (PUT /api/v2/consumer_portal/profile/consumer) Update consumer information for the logged-in consumer. Request --- ## Update Consumer (PUT /api/v2/consumers/{consumer_id}) Update consumer information such as contact details or payment methods. Request --- ## Update Consumer Communication Methods Update consumer communication methods for the logged-in consumer. Request --- ## Update Consumer Language Update the preferred language for the logged-in consumer. Request --- ## Update Consumer Profile Settings Replace organization consumer profile settings using full PUT semantics. This endpoint expects the complete ``UpdateConsumerProfileSettings`` payload. Omitted fields are not preserved and will cause validation errors because the body is treated as a full replacement, not a partial patch. ``null`` for non-nullable fields is rejected by validation. Request --- ## Update Contact Settings Update organization contact information settings. Request --- ## Update Coupon Update coupon details such as discount amount or active status. Request --- ## Update Customer Portal Branding Configuration Update customer portal branding colors. Request --- ## Update Customer Portal Branding Favicon Update customer portal favicon image. Request --- ## Update Discount Rule Update Discount Rule Request --- ## Update Email Template Configuration Update notification email colours, header mode, and nested sections. Request --- ## Update Email Template Header Image Upload a full-width header image and switch off logo-strip mode. The file is stored ``public-read``; only PNG, JPEG, WebP, or GIF (up to 5MB) are accepted after PIL decode validation so arbitrary HTML/SVG/scripts cannot be hosted. Request --- ## Update Form (PATCH /api/v2/forms/{form_id}) Update Form Request --- ## Update Form (PATCH /api/v2/payment_link_forms/{form_id}) Update Form Request --- ## Update Form Submission Autosave form answers while the submission is a draft. Request --- ## Update Installment Rule Update Installment Rule Request --- ## Update Installment Settings Update installment payment settings for the organization. Request --- ## Update Invoice Update invoice details such as items, consumer, due date, or payment methods. Request --- ## Update Invoice In Place Partially update specific invoice fields in-place. This endpoint allows updating only the fields specified in the request. Use for quick changes such as updating the description or scheduled date. Request --- ## Update Organization Accounting Posting Behavior Update the organization's accounting posting settings (only the fields sent). Request --- ## Update Organization Address Create or replace the organization's national address (used as the seller address on ZATCA invoices). Request --- ## Update Organization Name Update the organization name. Request --- ## Update Organization Preferences Update Organization Preferences Request --- ## Update Organization Refund Policy Update the organization refund policy by creating a new policy version. Request --- ## Update Payment (PUT /api/v2/consumer_portal/card/payment) Update payment information for a card payment. Request --- ## Update Payment (PUT /api/v2/payments/{payment_id}) Update payment details such as amount or payment method. Request --- ## Update Payment Flow Update the payment flow configuration for a consent. Request --- ## Update Payment Link Coupons Update link-level and item-level coupons on a payment link. Recomputes totals. New checkouts use the updated coupons; payments already in progress are unchanged. Request --- ## Update Payment Link Installment Settings Update payment link installment settings for the organization. Request --- ## Update Payment Link Payment Update payment information for a payment link card payment. Request --- ## Update Payment Link Payment Options Update organization payment link payment options settings. Request --- ## Update Payment Link Status Update the status of a payment link. Allows activating or deactivating a payment link. When deactivated, the link will no longer accept new payments. Existing payments in progress are not affected. Request --- ## Update Payment Options Update organization payment options settings. Request --- ## Update Pricing Table Update Pricing Table Request --- ## Update Product Update product details such as name, price, or description. Request --- ## Update Product Price Update a product price. This follows an immutable pricing pattern: the old price is archived and a new price is created with the updated values. Request --- ## Update Product Switch Group Replace name and full product membership list. Request --- ## Update Quantity Change Group Update a quantity-change group. ``product_ids`` fully replaces the group's existing product list. Sending an empty ``product_ids`` list deletes the group. Request --- ## Update Risky Invoices Settings Replace the organization's risky invoices buckets (low/medium/high). Request --- ## Update Subscription Update subscription items, coupons, billing interval, description, auto-cancel cycles, or payment methods. Updatable fields: - **Items** (products and quantities) — deferred to the next cycle. - **Subscription-level coupons** — same deferral as items. - **Recurring interval / interval count** (e.g. monthly ↔ yearly) — deferred to the next cycle. - **Description** — applied immediately. - **Until cycle number** (auto-cancel) — applied immediately. - **Payment method overrides** — applied immediately. Any change to items, coupons, or interval is recorded as a *pending change* that takes effect at ``current_period_end``. The response carries a ``pending_change`` block describing the deferred change, including a ``next_invoice_preview`` and the per-line diff. If the next-cycle invoice has already been generated and is unpaid, it is automatically cancelled and re-issued with the new amount. To change anything else (e.g. the consumer / payer), cancel the subscription and create a new one. Use ``DELETE /pending-change`` to revert a previously scheduled change before it takes effect. Request --- ## Update Subscription Auto Renewal Enable or disable auto-renewal for a subscription. Request --- ## Update Subscription Auto Renewal Public Public endpoint for consumers to enable/disable auto-renewal for their subscriptions without authentication. Requires both subscription_id and organization_consumer_id to verify ownership. Request --- ## Update Subscription Freeze Update an existing subscription freeze period. Allows modifying the start and end dates of a freeze period. The subscription will remain frozen during the updated period. Request --- ## Update Template Config Update invoice template configuration. Request --- ## Update Template Header Image Update invoice template header image. Request --- ## Upload Form Submission Document Upload a private document for a form submission (PDF/image, max 10MB, max 10 files). Request --- ## Upload Payment Link Document Upload a document (PDF or image, max 10MB) to attach to a payment link at creation time. Returns the document id to pass in the payment link create payload as `document_ids`. Request --- ## Upsert Organization Config Upsert Organization Config Request --- ## Vat Number Update the organization vat number. Request --- ## Verify An Mfa Against An Identifier Verify the multi-factor authentication code for identity confirmation. Request --- ## Verify Forgot Password Verify the password reset code sent to user email. Request --- ## Verify Pending User Verify user email with verification code and complete registration. Request --- ## Wipe Organization Data Hard-delete transactional data for a test organization (E2E cleanup). Inherits router gates: non-production + `x-test-support-key`, private schema only. Org must have ``settings.is_test_organization=True`` (independent of sandbox). Deletes invoices/payments, subscriptions, payment links, customers, products, notifications, and related rows. Keeps the organization shell (users, branches, settings, accounts). Request --- ## Withdraw Scope Request Withdraw Scope Request Request --- ## Zatca Eligibility What the organization must complete (VAT number, CR / FL number, national address) to connect ZATCA. --- ## Zatca Onboarding Status Zatca Onboarding Status --- ## Express SDK For the complete Express SDK documentation, please visit our GitHub repository: **[📦 @streamsdk/express on GitHub →](https://github.com/streampayments/streamsdk-express)** ## Quick Links - **[Installation & Quick Start](https://github.com/streampayments/streamsdk-express#installation)** - **[API Documentation](https://github.com/streampayments/streamsdk-express#api-documentation)** - **[Code Examples](https://github.com/streampayments/streamsdk-express/tree/main/examples)** - **[Demo Application](https://github.com/streampayments/stream-express-sdk-demo)** - **[npm Package](https://www.npmjs.com/package/@streamsdk/express)** ## Overview The Stream Express SDK provides a clean, declarative way to integrate Stream payments into your Express.js applications. Built on top of [@streamsdk/typescript](https://www.npmjs.com/package/@streamsdk/typescript), it offers drop-in handlers for checkout flows and webhook processing. **Key Benefits:** - 🚀 **Simple Integration** - Add payment processing in minutes - 🔒 **Type-Safe** - Full TypeScript support with type definitions - 🎯 **Event-Driven** - Clean event handlers for webhook processing - 🔄 **Flexible** - Supports guest and authenticated checkout flows - 📦 **Lightweight** - Minimal dependencies (~7KB) - 🔗 **Auto-Updates** - Inherits stream-sdk updates automatically ## Installation ```bash npm install @streamsdk/express ``` ## Quick Example ```typescript const app = express(); app.use(express.json()); // Payment link handler app.get("/checkout", Checkout({ apiKey: process.env.STREAM_API_KEY!, successUrl: "https://myapp.com/success", returnUrl: "https://myapp.com/cancel", })); // Webhook handler with events app.post("/webhooks/stream", Webhooks({ apiKey: process.env.STREAM_API_KEY!, onPaymentSucceeded: async (data) => { console.log("Payment succeeded:", data); // Update your database, send confirmation emails, etc. }, onPaymentFailed: async (data) => { console.log("Payment failed:", data); }, onInvoiceCreated: async (data) => { console.log("Invoice created:", data); }, })); app.listen(3000); ``` For complete documentation, examples, and API reference, visit the [GitHub repository](https://github.com/streampayments/streamsdk-express). ## Demo Application Want to see it in action? Clone and run the **[Express SDK demo repository](https://github.com/streampayments/stream-express-sdk-demo)** locally for a quick working example. ## Related SDKs - **[TypeScript SDK](/sdks/typescript)** - Core SDK for Node.js/TypeScript - **[API Reference](/api/v2-payment-links-create)** - REST API documentation - **[Webhooks Guide](/webhooks)** - Webhook event reference --- ## TypeScript SDK For the complete TypeScript SDK documentation, please visit our GitHub repository: **[📦 @streamsdk/typescript on GitHub →](https://github.com/streampayments/streamsdk-typescript)** ## Quick Links - **[Installation & Quick Start](https://github.com/streampayments/streamsdk-typescript#installation)** - **[API Documentation](https://github.com/streampayments/streamsdk-typescript#api-documentation)** - **[Code Examples](https://github.com/streampayments/streamsdk-typescript/tree/main/examples)** - **[npm Package](https://www.npmjs.com/package/@streamsdk/typescript)** ## Overview The Stream TypeScript SDK provides a complete Node.js/TypeScript interface for: - 🔐 Secure API Key authentication - 👥 Customer (Consumer) management - 📦 Product catalog management - 💳 Payment link creation - 🔄 Subscription handling - 🧾 Invoice generation - 🎟️ Coupon & discount management ## Installation ```bash npm install @streamsdk/typescript ``` ## Quick Example ```typescript const client = StreamSDK.init(process.env.STREAM_API_KEY!); const result = await client.createSimplePaymentLink({ name: "Monthly Subscription", amount: 99.99, consumer: { email: "customer@example.com", name: "Ahmad Ali", phone: "+966501234567", }, product: { name: "Premium Plan", price: 99.99, }, successRedirectUrl: "https://yourapp.com/success", failureRedirectUrl: "https://yourapp.com/failure", }); console.log("Payment URL:", result.paymentUrl); ``` For complete documentation, examples, and API reference, visit the [GitHub repository](https://github.com/streampayments/streamsdk-typescript). ## Related SDKs - **[Express.js SDK](/sdks/express)** - Framework adapter for Express.js applications - **[API Reference](/api/v2-payment-links-create)** - REST API documentation