Recurring payments
Save a card token once, then charge it each billing cycle
Charge a customer on a schedule by saving their card once. There are two phases:
- Initial setup — the customer pays and you store the returned token.
- Recurring charges — you charge that token each cycle.
The customer is involved only in the first payment.
There is no subscriptions API
Kashier does not offer a managed subscriptions or billing-plans API. You own the billing schedule: save a card token once, then charge it yourself each cycle with the Pay with Token API.
APIs used
- Save Token API — initiates the recurring flow, displays the payment UI, and creates the token and agreement.
- Pay with Token API — charges the customer using previously generated credentials.
Phase 1: Initial setup (Save Token API)
Step 1: Initiate setup
The merchant backend calls the Save Token API to start the recurring flow and show the Kashier payment UI.
Step 2: Customer completes payment
The customer enters card details and completes the payment. Kashier generates:
- cardDataToken
- agreementId
Step 3: Receive webhook
After a successful payment, Kashier sends a webhook containing the cardDataToken and agreementId.
Step 4: Store credentials
The merchant validates the webhook and securely stores the token and agreement data.
Phase 2: Recurring charges (Pay with Token API)
interactionSource changes between phases
interactionSource is RECURRING while saving the token during setup (see the Save Token field reference) and CONTAUTH while charging the stored token (see the Pay with Tokens field reference).
Step 5: Charge customer
For each subscription cycle, the merchant calls the Pay with Token API using the stored cardDataToken and agreementId.
Step 6: Handle result
Kashier processes the charge and returns the payment result. The merchant updates the subscription status accordingly.
Summary
- Call the Save Token API
- Customer completes payment
- Receive webhook with token and agreement
- Store credentials
- Use the Pay with Token API for recurring billing
Download the Kashier recurring APIs Postman collection
List tokens
Parameters
| Parameter | Type | Description |
|---|---|---|
| sortType | Query Parameter | Used to sort the results (-1 = DESC, 1 = ASC). |
| page | Query Parameter | Current page number (used for pagination). |
| limit | Query Parameter | Number of items per page (used for pagination). |
Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from the Kashier dashboard. Learn more about Authorization. |
curl --location 'https://test-api.kashier.io/v3/payment/tokenization/tokens?sortType=-1&page=1&limit=20'
--header 'Authorization: YOUR_TEST_SECRET_KEY'Response structure
{
"message": "success",
"data": [
{
"cardHolderName": "Hegab",
"token": "a559ecc6-11a2-431a-b40c-c10df4e7a9df",
"agreementId": "65d4717c2b2c17002469e463",
"customerReference": "12345",
"agreementType": "UNSCHEDULED",
"agreementExpiry": "N/A",
"cardNumber": "5123********2346"
},
{
"cardHolderName": "Hussein Mo",
"token": "efca2dc1-ea90-480b-99ae-00f072fbc516",
"agreementId": "668bc3b9b2c0fe0024a37af0",
"customerReference": "12345",
"agreementType": "UNSCHEDULED",
"agreementExpiry": "2024-12-13T13:32:05.000Z",
"cardNumber": "5123********0008"
},
{
"cardHolderName": "TEST",
"token": "c34faed2-b1e3-4ebf-99ab-992ef2f70d61",
"agreementId": "66043d39ec69ad0024b9c012",
"customerReference": "123456",
"agreementType": "RECURRING",
"agreementExpiry": "2024-11-14T15:47:29.448Z",
"cardNumber": "5078********5581"
}
],
"pagination": {
"total": 3,
"limit": 20,
"page": 1,
"pages": 1
}
}RECURRING vs UNSCHEDULED agreement types
The response above shows tokens with agreementType set to both RECURRING and UNSCHEDULED — the same values appear as agreement.type in the Save token request example. What's confirmed is that both are valid agreement.type / agreementType values:
| Value | Confirmed meaning |
|---|---|
RECURRING | A scheduled recurring agreement — billed on a fixed schedule (see paymentFrequency in the Save token example). |
UNSCHEDULED | A card-on-file agreement without a fixed billing schedule. |
Beyond this, no further guidance on choosing between the two is documented — treat the table above as what's confirmed, not a complete decision guide.
Save token
Saving a card is a payment session create call with an agreement object and saveCard set — the session renders the payment UI, and the token and agreement come back on the webhook once the customer pays.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v3/payment/sessions |
| LIVE-URL | https://api.kashier.io/v3/payment/sessions |
| Method | POST |
Headers
| Key | Description |
|---|---|
| Authorization | Your Secret Key, used to identify the merchant. You can obtain it from the Kashier dashboard. Learn more about Authorization. |
| api-key | Your Payment API Key, from the merchant dashboard under the Integrations section. It differs between the test and live environments. |
| Content-Type | application/json |
curl --location 'https://test-api.kashier.io/v3/payment/sessions'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--header 'api-key: YOUR_TEST_API_KEY'
--data '{
"expireAt": "2027-10-31T17:27:32.359Z",
"maxFailureAttempts": 3,
"paymentType": "credit",
"amount": "1",
"currency": "EGP",
"merchantId": "MID-XXXXX-XXX",
"merchantOrderId": "ORD-123456",
"merchantRedirect": "https://your-website.com/redirect",
"display": "en",
"type": "one-time",
"allowedMethods": "card",
"redirectMethod": null,
"iframeBackgroundColor": "#FFFFFF",
"metaData": {
"customKey": "customValue"
},
"failureRedirect": false,
"brandColor": "#FF5733",
"defaultMethod": "card",
"description": "Payment for order ORD-123456",
"manualCapture": false,
"customer": {
"reference": "ahmed"
},
"agreement": {
"type": "RECURRING",
"amountVariability": "VARIABLE",
"paymentFrequency": "MONTHLY",
"expiryDate": "2027-01-27",
"minimumDaysBetweenPayments": 20,
"maximumAmountPerPayment": 20000,
"numberOfPayments": 100
},
"saveCard": "forced",
"retrieveSavedCard": true,
"interactionSource": "RECURRING",
"enable3DS": true,
"serverWebhook": "https://your-website.com/kashier-webhook",
"notes": "test"
}'Body description
| Parameter | Description | Required |
|---|---|---|
| expireAt (string) | When the payment session expires. | TRUE |
| maxFailureAttempts (integer) | Maximum number of failed payment attempts. | TRUE |
| amount (string) | Order amount, e.g., 100. | TRUE |
| currency (string) | Order currency, e.g., EGP. | TRUE |
| merchantOrderId (string) | Unique order identifier. | TRUE |
| merchantId (string) | Merchant account number or merchant ID, e.g., MID-123-123. | TRUE |
| metaData (object) | Order metadata, as a JSON object of key/value pairs — see the sample body above. | FALSE |
| description (string) | Order description; must be less than 120 characters. | FALSE |
| allowedMethods (string) | Defines allowed payment methods. Omit it to fall back to the default method list configured for your account. If you want to display only a specific provider (for example, Valu or QNB), you can do so by specifying it within array brackets alongside its payment method. For example: bnpl[valu], bank_installments[qnb], and so on. In test mode the payment UI shows only card and wallet regardless of what you send. | FALSE |
| metaData.displayNote | An object with key:value pairs, e.g. "metaData": {"customKey": "customValue","displayNotes": {"key": "value"}}. The note will be displayed on the payment UI screen. | FALSE |
| defaultMethod (string) | Determines which method the payment UI opens on. By default it opens on the card method. defaultMethod accepts the pattern "method,provider/abbreviation". For example, to open the payment UI on the installment method, the defaultMethod value should be bank_installments. To open it on certain installment plans of NBE bank, the value should be bank_installments,NBE. | FALSE |
| merchantRedirect (string) | merchantRedirect should be URI encoded: urlencode(https://www.your_website.com/redirect). | TRUE |
| serverWebhook (string) | Pass an endpoint to receive server-to-server notifications. On your application this is as easy as creating a new page that accepts unauthenticated POST requests. The event object is sent as JSON in the request body. See Webhook. | FALSE |
| redirectMethod (string) | The redirection method after payment. Default method is get, e.g. redirectMethod="post". | FALSE |
| failureRedirect (string) | Whether to redirect after the first payment failure. Its value is TRUE or FALSE; default value is TRUE. | FALSE |
| type (string) | Defines the type of Kashier origin. Default: one-time. | TRUE |
| brandColor (string) | Specifies branding color using a hex or rgba value. Default: rgba(45, 164, 78, 0.9). | FALSE |
| display (string) | Specifies the display language of the iframe. Options: ar or en. | TRUE |
| manualCapture (bool) | TRUE: Authorize first, then capture/release. FALSE: Direct capture without authorization. | FALSE |
| customer (string) | An object that contains the customer information. Please note that it should include customer.reference, which will be used to link the merchant's customer with the payment's customer, and it should only be included when saveCard is optional or forced: "customer": {"email": "[email protected] ","reference": "65489463"}. | FALSE |
| saveCard (string) | Defines if card information is saved. Options: optional or forced. | FALSE |
| interactionSource (string) | Must be RECURRING when saving a card for repeat payments. | FALSE |
| enable3DS (bool) | TRUE: Enables 3DS for saved card payments. FALSE: Disables 3DS. | FALSE |
| notes (string) | Add any additional information about this payment. This could include special instructions, or context for the recipient. | FALSE |
| agreement | The agreement object defines the parameters of a recurring payment arrangement. Its fields include: - type (string): Specifies the type of agreement. In this case, "RECURRING" indicates that payments will occur periodically. - amountVariability (string): Determines whether the payment amounts are fixed or variable. "VARIABLE" means the payment amount can change for each cycle. - paymentFrequency (string): Indicates how often payments occur. "MONTHLY" means the payment is scheduled once every month. - expiryDate (string): The date when the agreement ends, formatted as YYYY-MM-DD. Here, the agreement expires on "2027-01-27". - minimumDaysBetweenPayments (integer): The minimum number of days required between consecutive payments. In this example, it is 20 days. - maximumAmountPerPayment (integer): The upper limit for each payment amount. The maximum here is 20000 (currency unit depends on implementation). - numberOfPayments (integer): The total number of payments allowed under this agreement. Here, it is set to 100. | FALSE |
Response structure
{
"status": "CREATED",
"failureAttempts": 0,
"capturedAmount": 0,
"refundedAmount": 0,
"_id": "67adc07584f10c00121f6739",
"merchantId": "MID-XXXXX-XXX",
"expireAt": "2027-10-31T17:27:32.359Z",
"maxFailureAttempts": 3,
"paymentParams": {
"paymentType": "credit",
"amount": "1",
"currency": "EGP",
"merchantOrderId": "ORD-123456",
"merchantRedirect": "https://your-website.com/redirect",
"display": "en",
"type": "one-time",
"allowedMethods": "card",
"customer": { "reference": "ahmed" },
"agreement": {
"type": "RECURRING",
"amountVariability": "VARIABLE",
"paymentFrequency": "MONTHLY",
"expiryDate": "2027-01-27",
"minimumDaysBetweenPayments": 20,
"maximumAmountPerPayment": 20000,
"numberOfPayments": 100
},
"saveCard": "forced",
"retrieveSavedCard": true,
"interactionSource": "RECURRING",
"enable3DS": true,
"serverWebhook": "https://your-website.com/kashier-webhook"
},
"history": [
{ "status": "CREATED", "date": "2026-08-25T09:50:45.360Z" }
],
"webhookNotifications": [],
"createdAt": "2026-08-25T09:50:45.363Z",
"updatedAt": "2026-08-25T09:50:45.363Z",
"sessionUrl": "https://payments.kashier.io/session/67adc07584f10c00121f6739?mode=test"
}Send the customer to sessionUrl — or render it in an iframe, as in payment sessions. The session is created in CREATED state; no card is saved and no token exists yet.
The token arrives on the webhook, not here
This call only creates the session. The cardToken and the confirmed agreement are delivered to your serverWebhook once the customer completes payment — see webhook payloads. Do not expect a token in this response.
Pay with tokens
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-fep.kashier.io/v3/orders |
| LIVE-URL | https://fep.kashier.io/v3/orders |
| Method | POST |
Headers
| Key | Description |
|---|---|
| Kashier-Hash | Order hash generated in hashing. Append .{customerReference} to the signed string — it is required whenever card.save, card.cardToken, or card.agreement is present. |
curl --location 'https://test-fep.kashier.io/v3/orders'
--header 'kashier-hash: {{hash}}'
--header 'Content-Type: application/json'
--data '{
"apiOperation":"PAY",
"paymentMethod":{
"type":"CARD",
"card":{
"agreement":{
"id":"03c331af-88b5-47ed-be28-6c671e760d5c"
},
"enable3DS":false,
"cardToken":"c3a64de8-c172-40e6-8412-0bb2e2b7e895"
}
},
"order":{
"reference":"PM-1756736464046963252",
"amount":100,
"currency":"EGP"
},
"shipping":{
"address":{
"city":null,
"company":null,
"country":null,
"postcodeZip":null,
"source":null,
"stateProvince":null,
"stateProvinceCode":null,
"street":null,
"street2":null,
"sameAsBilling":true
},
"method":"",
"origin":{
"postcodeZip":""
}
},
"billing":{
"address":{
"city":null,
"company":null,
"country":null,
"postcodeZip":null,
"source":null,
"stateProvince":null,
"stateProvinceCode":null,
"street":null,
"street2":null
}
},
"interactionSource":"CONTAUTH",
"reconciliation":{
"webhookUrl":"https://your-call-back-url.com",
"merchantRedirect":"https://your-call-back-url.com"
},
"merchantId":"MID-XXXXX-XXX",
"customer":{
"reference":"12345963852"
}
}'Full parameter and response reference → Pay with token.
Bulk charge
Bulk charge is off until Kashier enables it
Bulk charge is gated by the per-merchant bulk charge feature flag. While it is off, both bulk-charge endpoints reject the request with 400 and the message key message.bulk.charge.disabled, even though your secret key is valid. A new merchant account starts with only a small default set of features enabled, so assume bulk charge is off until you have asked Kashier ops to turn it on.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v3/payment/bulk-charge |
| LIVE-URL | https://api.kashier.io/v3/payment/bulk-charge |
| Method | POST |
Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from the Kashier dashboard. Learn more about Authorization. |
curl --location 'https://test-api.kashier.io/v3/payment/bulk-charge'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--form 'file=@"/home/k/Downloads/tokens_export.xlsx"'Response structure
{
"message": "success"
}Bulk charge runner
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v3/payment/bulk-charge/runner |
| LIVE-URL | https://api.kashier.io/v3/payment/bulk-charge/runner |
| Method | GET |
Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from the Kashier dashboard. Learn more about Authorization. |
curl --location 'https://test-api.kashier.io/v3/payment/bulk-charge/runner'
--header 'Authorization: YOUR_TEST_SECRET_KEY'Response structure
{
"message": "success"
}Export tokens
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v3/payment/tokenization/tokens/export |
| LIVE-URL | https://api.kashier.io/v3/payment/tokenization/tokens/export |
| Method | GET |
Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from the Kashier dashboard. Learn more about Authorization. |
curl --location 'https://test-api.kashier.io/v3/payment/tokenization/tokens/export'
--header 'Authorization: YOUR_TEST_SECRET_KEY'Response structure
The response is a file that will be automatically downloaded in your browser.
Delete token
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v3/payment/tokenization/tokens/:token |
| LIVE-URL | https://api.kashier.io/v3/payment/tokenization/tokens/:token |
| Method | DELETE |
Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from the Kashier dashboard. Learn more about Authorization. |
curl --location --request DELETE 'https://test-api.kashier.io/v3/payment/tokenization/tokens/:token'
--header 'Authorization: YOUR_TEST_SECRET_KEY'Response structure
{
"message": "success"
}