Payments
The Payment object, its statuses and how a payment moves from creation to a final result.
A Payment represents one attempt to collect money from a customer. You create it from your server, the customer pays on the hosted checkout, and the payment ends as succeeded or expired.
Lifecycle
requires_paymentCreated. Waiting for the customer to pay at checkout_url.
processingThe customer submitted payment. The result is not final yet.
succeededMoney captured. Final. Safe to fulfil. Refunds allowed.
failedDeclined or errored. See failure_code. Can still be paid at checkout_url until expires_at.
canceledThe customer canceled at checkout. Can still be paid until expires_at.
expiredNot paid before expires_at. Final.
| Status | Final | Meaning | What to do |
|---|---|---|---|
requires_payment | No | Created. Waiting for the customer at checkout_url. | Redirect the customer to checkout_url. |
processing | No | The customer submitted payment; the result is not known yet. | Show "We're confirming your payment" and wait for the webhook. |
succeeded | Yes | Money captured. | Fulfil the order. Refunds are possible. |
failed | No | Declined or errored. failure_code and failure_message explain why. | Let the customer try again at the same checkout_url until expires_at. |
canceled | No | The customer clicked Cancel at checkout. | Offer to try again at the same checkout_url. |
expired | Yes | Not paid before expires_at. | Create a new payment if the customer comes back. |
succeeded and expired are final and never change. failed and canceled are not: until expires_at, the customer can open the same checkout_url and pay again, and the payment can still become succeeded. Don't close the order on payment.failed or payment.canceled; wait for payment.succeeded or payment.expired.
Refunds don't change status: they increase amount_refunded instead. A payment with amount_refunded equal to amount is fully refunded; anything in between is partially refunded. See Refunds.
Each of these status changes sends a webhook event: payment.succeeded, payment.failed, payment.canceled or payment.expired.
Create a payment
POST /v1/payments
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | integer | Yes | Amount in minor units. 12500 with KWD is 12.500 KWD. |
currency | string | Yes | KWD, SAR, AED, QAR, BHD, OMR, JOD or EGP. |
return_url | string | Yes | URL the customer returns to after checkout, up to 2,000 characters. Must be https:// in live mode; test mode also accepts http://. See Return URL handling. |
description | string | No | Up to 500 characters. Shown to the customer at checkout and in your dashboard. |
reference | string | No | Your own ID, such as the order number, up to 100 characters. You can filter payments by it. |
customer | object | No | name, email, phone (international format, for example +96550000000). All optional. |
payment_method | string | No | Offer only this method at checkout: knet, mada, card, apple_pay, google_pay, benefit, stc_pay. Which ones you can use depends on your account; a method your account doesn't support returns 422 validation_error. See Payment methods and your account. |
language | string | No | Checkout language: en or ar. |
send_email | boolean | No | true emails the payment to customer.email as an invoice with a Pay now button. Default false. Requires customer.email. See Invoice and receipt emails. |
metadata | object | No | Up to 20 string key/value pairs. See Metadata. |
expires_in_minutes | integer | No | How long the customer has to pay, from 5 to 43200 (30 days). Default 60. |
On some accounts, amounts in three-decimal currencies (KWD, BHD, OMR, JOD) must end in 0, or the request fails with 422 validation_error (details.fields.amount). See Three-decimal amounts.
The response is 201 Created with the Payment. Send an Idempotency-Key header so retries never create a second payment (Idempotency).
The Payment object
{
"id": "pay_01JBX4T9W2",
"object": "payment",
"livemode": false,
"amount": 12500,
"currency": "KWD",
"amount_refunded": 0,
"status": "succeeded",
"description": "Order #1042",
"reference": "1042",
"customer": { "name": "Sara Ali", "email": "sara@example.com", "phone": "+96550000000" },
"payment_method": "card",
"card": { "brand": "visa", "last4": "1019" },
"channel": "api",
"checkout_url": "https://api.teyra.co/checkout/pay_01JBX4T9W2",
"return_url": "https://shop.example.com/thanks",
"failure_code": null,
"failure_message": null,
"commission": 313,
"net": 12187,
"metadata": { "order_id": "1042" },
"payment_link_id": null,
"created_at": "2026-10-28T14:07:00.000Z",
"paid_at": "2026-10-28T14:08:12.000Z",
"expires_at": "2026-10-28T15:07:00.000Z"
}| Field | Description |
|---|---|
id | Unique ID, prefixed pay_. |
livemode | false for test payments, true for live payments. |
amount, currency | What the customer pays, in minor units. |
amount_refunded | Total refunded so far, from 0 up to amount. |
status | See Lifecycle. |
payment_method | How the customer paid, once known: card, mada, knet, apple_pay, google_pay, benefit, stc_pay, or null. For Apple Pay and Google Pay, card holds the wallet card's brand and last four digits. |
card | For card payments, brand and last4 only. Teyra Pay never exposes full card numbers. |
channel | Where the payment came from: api, payment_link, shopify, woocommerce or dashboard. |
checkout_url | Teyra Pay hosted checkout for this payment. |
failure_code, failure_message | Why the payment failed, when status is failed. |
commission, net | Set once the payment succeeds, in minor units. commission is the Teyra Pay fee: your rate (a percentage plus a fixed amount per payment), the only fee taken from the payment. net is what you receive: amount − commission. See Fees and net amount. |
payment_link_id | The payment link that created this payment, if any. |
paid_at, expires_at | When the payment succeeded, and the deadline to pay. |
Retrieve and list payments
# One payment
curl https://api.teyra.co/v1/payments/pay_01JBX4T9W2 \
-H "Authorization: Bearer $TEYRA_SECRET_KEY"
# Succeeded payments created in October 2026
curl -G https://api.teyra.co/v1/payments \
-H "Authorization: Bearer $TEYRA_SECRET_KEY" \
-d status=succeeded \
-d created_gte=2026-10-01T00:00:00Z \
-d created_lte=2026-10-31T23:59:59Z \
-d limit=50GET /v1/payments filters: status, reference, created_gte, created_lte, plus limit and cursor (Pagination). An unknown ID, or one from the other mode, returns 404 payment_not_found.
Next
- Hosted checkout: the redirect flow step by step.
- Payment methods and your account: what depends on your account.
- Return URL handling: build the page customers come back to.
Test mode and live mode
Build against test mode with no real money, then switch to live keys once your account is approved, using the go-live checklist.
Payment methods and your account
Which payment_method values your account accepts, the amount rule for three-decimal currencies, and how checkout, fees, refunds and disputes work on each kind of account.