# 1. Getting Started

API for Israel-based businesses. All requests are **POST** to `https://allpay.to/app/?show=<endpoint>&mode=api12` with a JSON body. The `show` and `mode` parameters are passed in the URL query string — not in the JSON body — and are not included in the signature. Authentication uses your API login and a SHA256 signature.

## 1.1 Authentication {#authentication}

Every request must include two fields in the JSON body:

| Field | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | Your API login — found in Settings → Integrations |
| sign* | string | required | SHA256 signature of the request body (see algorithm below) |

## 1.2 Signature Algorithm {#signature}

The signature is generated from the request parameters using your private API key.

1. Remove the `sign` field from the request.
2. Exclude all parameters with empty values (`""`, `null`, missing).
3. Sort the remaining keys in **alphabetical order (A–Z)**. Alphabetical sorting must be applied everywhere — to top-level parameters, and to the keys inside each object within array parameters (such as `items` and `subscription`).
4. From the sorted list, take only the parameter values and join them into a single string using a colon `:` as the separator.
5. Append your API key to the end of the string, preceded by a colon.
6. Apply **SHA256** to the final string encoded as **UTF-8**.

**▶ Worked Example**

Request body (before signing):

```
{
  "items": [
    {
      "name": "Test payment",
      "price": "1000",
      "qty": "1",
      "discount_val": "0",
      "discount_type": "fixed",
      "vat": "1"
    }
  ],
  "order_id": "1589758an",
  "client_name": "Jason Statham",
  "client_email": "jason@company.io",
  "client_tehudat": "000000000",
  "currency": "ILS",
  "currency_display": "ILS",
  "lang": "AUTO",
  "preauthorize": "0",
  "allpay_token": "",       ← excluded (empty string)
  "inst": "",               ← excluded (empty string)
  "doc_type": "",           ← excluded (empty string)
  "button_title": "pay",
  "success_url": "https://example.com/success",
  "webhook_url": "https://example.com/hook/allpay",
  "backlink_url": "",       ← excluded (empty string)
  "login": "pp1008795"
}
```

Sorted keys A–Z with their values (items[] fields also sorted A–Z and flattened inline):

```
button_title      → "pay"
client_email      → "jason@company.io"
client_name       → "Jason Statham"
client_tehudat    → "000000000"
currency          → "ILS"
currency_display  → "ILS"
items[0]          → discount_type="fixed", discount_val="0", name="Test payment", price="1000", qty="1", vat="1"
lang              → "AUTO"
login             → "pp1008795"
order_id          → "1589758an"
preauthorize      → "0"  ← included ("0" is not empty)
success_url       → "https://example.com/success"
webhook_url       → "https://example.com/hook/allpay"
```

String to sign (values joined with `:`, API key appended):

```
pay:jason@company.io:Jason Statham:000000000:ILS:ILS:fixed:0:Test payment:1000:1:1:AUTO:pp1008795:1589758an:0:https://example.com/success:https://example.com/hook/allpay:CB545B50989469F7258C8E043462B30C
```

SHA256 result:

```
c3c5458724d88837bd60879b5130c860589e42248360b4ca42c2539ebf8a7803
```

> ⚠️ Make sure you follow these steps exactly. 99% of "Invalid signature" errors are caused by: trimming spaces from values, treating `0` as empty (it is a valid value and **is included** in the signature), or omitting parameters from the signature function. Empty values are `""`, `null`, and missing fields only.

## 1.3 Errors {#errors}

All errors return HTTP 200 with a JSON body containing `error_code` and `error_msg`:

```
{
  "error_code": 3,
  "error_msg":  "Signature is incorrect"
}
```

| error_code | error_msg | Description |
| --- | --- | --- |
| 2 | `Missing required parameters: ...` | A required parameter is missing, has an invalid value, or failed validation (price, qty, discount, email format, `doc_type` eligibility). The error message lists the specific fields. |
| 3 | `Signature is incorrect` | The `sign` parameter does not match the expected SHA256 signature. |
| 5 | `Login incorrect` | The `login` parameter does not match any Allpay account. |
| 6 | `Order not found` / `Subscription not found` | No order or subscription with the given `order_id` was found for this account. |
| 9 | `Incorrect token` | The `allpay_token` does not exist or is invalid. |
| 12 | `<processor message>` | The payment processor declined the token charge. The message contains the processor's reason. |
| 13 | `Token creation error` | Failed to retrieve a token for the given order (`gettoken` endpoint). |
| 15 | `Order ID ... has already been paid` | An order with this `order_id` already exists and has been paid. Use a unique `order_id` for each payment. |
| 16 | `Same order can not be charged twice` / `Refund error: wrong order status` | Attempted to charge a J5 pre-authorization that was already captured, or to refund an order that is not in a paid state. |
| 17 | `Processor refund error: ...` | The payment processor returned an error when processing the refund. |
| 18 | `Subscription can only include one item` | The `items` array contains more than one item in a subscription request. |
| 19 | `Payment error: wrong order status` | The payment processor returned an unexpected status when capturing a J5 pre-authorization. |
| 20 | `Incorrect method` | The `show` parameter in the URL contains an unknown or unsupported endpoint name. |
| 21 | `Amount must be at least 5 ILS` | The total payment amount (after applying currency rate) is below the minimum of 5 ILS. Applies only to ILS payments (`currency` = ILS or not specified). |

**Node.js:**
```javascript
const crypto = require('crypto');

function allpaySign(data, apiKey) {
  const d = Object.fromEntries(
    Object.entries({...data})
      .filter(([k, v]) => k !== 'sign' && v !== '' && v != null)
  );
  const chunks = [];
  for (const key of Object.keys(d).sort()) {
    const val = d[key];
    if (Array.isArray(val)) {
      for (const item of val)
        for (const k of Object.keys(item).sort())
          if (item[k] !== '' && item[k] != null)
            chunks.push(String(item[k]));
    } else {
      chunks.push(String(val));
    }
  }
  chunks.push(apiKey);
  return crypto
    .createHash('sha256')
    .update(chunks.join(':'))
    .digest('hex');
}
```

**PHP:**
```php
function allpay_sign(array $data, string $api_key): string {
  unset($data['sign']);
  $data = array_filter($data, fn($v) => $v !== '' && $v !== null);
  ksort($data);
  $chunks = [];
  foreach ($data as $val) {
    if (is_array($val)) {
      foreach ($val as $item) {
        ksort($item);
        foreach ($item as $v)
          if ($v !== '' && $v !== null) $chunks[] = (string)$v;
      }
    } else {
      $chunks[] = (string)$val;
    }
  }
  $chunks[] = $api_key;
  return hash('sha256', implode(':', $chunks));
}
```

**Python:**
```python
import hashlib

def allpay_sign(data: dict, api_key: str) -> str:
    d = {k: v for k, v in data.items()
         if k != 'sign' and v not in ('', None)}
    chunks = []
    for key in sorted(d):
        val = d[key]
        if isinstance(val, list):
            for item in val:
                for k in sorted(item):
                    if item[k] not in ('', None):
                        chunks.append(str(item[k]))
        else:
            chunks.append(str(val))
    chunks.append(api_key)
    return hashlib.sha256(':'.join(chunks).encode()).hexdigest()
```

# 2. Integration Flows {#integration-flows}

Choose the flow that matches your use case. Each links to the relevant endpoints.

*Most common*

### Redirect Payment

1. Call Create Payment → receive `payment_url`
2. Redirect the customer to `payment_url`
3. Customer enters card details on the Allpay payment page
4. Allpay POSTs a webhook to your server → verify signature → fulfill order

Best for: standard e-commerce checkout.

*Embedded form*

### Hosted Fields

1. Call Create Payment with `client_name` and `client_email` → receive `payment_url`
2. Embed the Hosted Fields iframe on your page using that URL
3. Customer enters card details directly on your site
4. Allpay POSTs a webhook → verify signature → fulfill order

Best for: keeping customers on your site. [Hosted Fields guide →](https://www.allpay.co.il/help/hosted-fields)

*Platform-managed billing*

### Token Billing

1. Obtain an `allpay_token`: use Capture Token to save a card without charging, or Get Token from a completed payment
2. Store the token securely on your side
3. Whenever you need to charge, call Create Payment with `allpay_token`
4. The charge is processed immediately — result returned synchronously; a webhook is sent to `webhook_url` (if provided)

Best for: SaaS, marketplaces — when *you* control the billing schedule and amounts.

*Allpay-managed billing*

### Native Allpay Subscription

1. Call Create Subscription → redirect customer to `payment_url`
2. Customer authorizes the first charge
3. Allpay charges automatically every month and sends a webhook for each charge
4. Manage via Cancel, Status, and List endpoints

Best for: fixed monthly subscriptions — no billing logic needed on your side.

*Two-step payment*

### Pre-authorization (J5)

1. Call Create Payment with `preauthorize: true` → customer authorizes the reservation
2. Allpay sends a webhook — amount is reserved on the card, not charged
3. Within 7 days: call Charge Pre-auth to capture (up to reserved amount) — or Refund to void

Best for: deliveries, rentals, variable-weight goods — when the final amount is unknown at authorization time.

# 3. Payments

Create payment links, check status, issue refunds.

## 3.1 Create Payment {#create-payment}

`POST https://allpay.to/app/?show=getpayment&mode=api12`

The payment process follows three steps: (1) send a signed POST request to create a payment link, (2) redirect the customer to the returned `payment_url`, (3) receive a webhook notification at your `webhook_url` once payment is complete.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | Your API login from Settings → Integrations |
| order_id* | string | required | Unique order identifier in your system. Use a unique `order_id` for each new payment — do not reuse an `order_id` from a previous paid payment (see error 15). |
| items* | array | required | List of products/services. Displayed in your Allpay account and on accounting documents. The total charge is calculated from item prices × quantities.<br>name* — string — required — Product or service name<br>qty* — number — required — Quantity<br>price* — number — required — Unit price. VAT must already be included in the price — it is not added on top.<br>vat* — integer — required — VAT rate included in the price: `0` — no VAT (VAT-exempt), `1` — 18% VAT, `3` — 0% VAT<br>discount_val — number — optional — Discount value — deducted from item price<br>discount_type — string — optional — Whether `discount_val` is a fixed amount or a percentage: `fixed` or `perc`. Required when `discount_val` is provided. |
| sign* | string | required | SHA256 signature |
| currency | string | optional | The billing (settlement) currency — the currency in which the customer is charged and your account is settled. If your account does not have permission to process USD or EUR, the amount is automatically converted to ILS using Google Finance exchange rates.<br>ILS; USD; EUR |
| currency_display | string | optional | The display currency shown to the customer on the payment page. The price values in the request must be provided in this currency — Allpay automatically converts the amount to the billing `currency`. Examples: `CAD`, `AED`, `RUB`. [Full list of supported currencies →](https://www.allpay.co.il/help/currencies-list-and-api-requests) When `currency_display` is used, the webhook `amount` reflects the display currency value (as specified in the request items), while the `currency` field reflects the billing currency — they refer to different currencies. |
| lang | string | optional | Payment page language.<br>AUTO; EN; HE; RU; AR; ES; IT; DE; FR |
| button_title | string | optional | Text on the payment button.<br>pay; donate; subscribe |
| doc_type | integer | optional | Type of the document issued after a successful payment — applies to one-time payments, token payments, and subscription charges. If not provided, the default value is taken from your account settings.<br>320 — Tax Invoice Receipt; 400 — Receipt; 405 — Receipt for donation<br>⚠️ The payment will **not** be created and an error will be returned if: the account has no connected accounting service (EasyCount or Morning), or the requested document type is one your business is not eligible to issue. |
| webhook_url | string | optional | After a successful or failed payment, Allpay sends a POST webhook with payment details to this URL. If not provided, the transaction will be visible only in your Allpay dashboard. |
| success_url | string | optional | The customer is redirected to this URL after successful payment. If not provided, the customer is redirected to the default Allpay success page. |
| backlink_url | string | optional | URL for the "Return to site" button displayed at the bottom of the payment page — allows the customer to return to your checkout. Note: there is no fail URL — payment errors are displayed directly on the payment page, prompting the customer to make a new attempt. |
| inst | integer | optional | Max installment payments offered (1–12) |
| inst_fixed | integer | optional | 0 — customer chooses from 1 up to the inst value; 1 — number of payments is fixed at the inst value; customer cannot change it |
| allpay_token | string | optional | Charge a card using a saved token — the customer does not need to re-enter card details. The payment is processed immediately and the result is returned synchronously in the API response. A webhook is sent to `webhook_url` (if provided), same as for regular payments. Use a new, unique `order_id` for each charge — do not reuse `order_id`s across recurring charges. On success: `{"order_id","status":1}`. On failure: `{"error_code","error_msg"}`. |
| preauthorize | boolean | optional | If `true`, creates a J5 pre-authorization — the amount is reserved on the customer's card for up to 168 hours (7 days), but no charge is made. To collect the funds, a separate Charge Pre-auth request must be sent within this period. |
| subscription | object | optional | Add to create a recurring subscription. See Create Subscription. |
| client_name | string | optional | Customer's full name (any language). If not provided, the customer will be asked to enter it on the payment page. Required when using Hosted Fields integration. |
| client_email | string | optional | Customer's email address. Used to send an invoice if an accounting service (EasyCount or Morning) is connected. If not provided, the customer will be asked to enter it on the payment page. Required when using Hosted Fields integration. |
| client_phone | string | optional | Customer's phone number. |
| client_tehudat | string | optional | For private customers — Social ID (Tehudat Zehut); for companies — Company Number (Mispar Het Pey). Pass `"000000000"` to hide this field and skip the ID request for non-Israeli customers. |
| show_applepay | boolean | optional | Show Apple Pay button (module must be active) |
| show_bit | boolean | optional | Show Bit payment button (module must be active) |
| add_field_1 | string | optional | Any additional data about the order or customer — returned unchanged in the webhook. |
| add_field_2 | string | optional | Any additional data about the order or customer — returned unchanged in the webhook. |
| expire | integer | optional | Unix timestamp when the payment link expires (default: 1 week) |

### Response

Redirect flow (no `allpay_token`):

| Field | Type | Description |
| --- | --- | --- |
| payment_url | string | URL to redirect the customer to |

Token payment flow (`allpay_token` provided) — on success:

| Field | Type | Description |
| --- | --- | --- |
| order_id | string | Order identifier |
| status | integer | `1` — charge successful |

A webhook is sent to `webhook_url` (if provided) — identical in structure to webhooks from regular (redirect-flow) payments.

On failure:

| Field | Type | Description |
| --- | --- | --- |
| error_code | integer | Error code from the payment processor |
| error_msg | string | Human-readable error description |

**Response:**
```json
// Redirect flow
{ "payment_url": "https://allpay.to/~login/pay/?payment_id=abc123&code=xyz" }

// Token flow — success
{ "order_id": "ORDER-001", "status": 1 }

// Token flow — failure
{ "error_code": 12, "error_msg": "Payment declined" }
```

**cURL:**
```bash
curl https://allpay.to/app/?show=getpayment&mode=api12 \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "ORDER-001",
    "items": [{
      "name": "Product A",
      "qty": 1,
      "price": 100,
      "vat": 1
    }],
    "currency": "ILS",
    "webhook_url": "https://yoursite.com/webhook",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**Node.js:**
```javascript
const crypto = require('crypto');

function allpaySign(data, apiKey) {
  const d = Object.fromEntries(
    Object.entries({...data})
      .filter(([k, v]) => k !== 'sign' && v !== '' && v != null)
  );
  const chunks = [];
  for (const key of Object.keys(d).sort()) {
    const val = d[key];
    if (Array.isArray(val)) {
      for (const item of val)
        for (const k of Object.keys(item).sort())
          if (item[k] !== '' && item[k] != null) chunks.push(String(item[k]));
    } else chunks.push(String(val));
  }
  chunks.push(apiKey);
  return crypto.createHash('sha256').update(chunks.join(':')).digest('hex');
}

const body = {
  login: 'your_login',
  order_id: 'ORDER-001',
  items: [{ name: 'Product A', qty: '1', price: '100', vat: '1' }],
  currency: 'ILS',
  webhook_url: 'https://yoursite.com/webhook'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');

const res = await fetch('https://allpay.to/app/?show=getpayment&mode=api12', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
});
const { payment_url } = await res.json();
```

**PHP:**
```php
function allpay_sign(array $data, string $api_key): string {
  unset($data['sign']);
  $data = array_filter($data, fn($v) => $v !== '' && $v !== null);
  ksort($data);
  $chunks = [];
  foreach ($data as $val) {
    if (is_array($val)) {
      foreach ($val as $item) {
        ksort($item);
        foreach ($item as $v)
          if ($v !== '' && $v !== null) $chunks[] = (string)$v;
      }
    } else { $chunks[] = (string)$val; }
  }
  $chunks[] = $api_key;
  return hash('sha256', implode(':', $chunks));
}

$body = [
  'login'    => 'your_login',
  'order_id' => 'ORDER-001',
  'items'    => [[
    'name' => 'Product A', 'qty' => '1',
    'price' => '100',       'vat' => '1'
  ]],
  'currency'    => 'ILS',
  'webhook_url' => 'https://yoursite.com/webhook'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');

$ch = curl_init('https://allpay.to/app/?show=getpayment&mode=api12');
curl_setopt_array($ch, [
  CURLOPT_POST           => true,
  CURLOPT_POSTFIELDS     => json_encode($body),
  CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
  CURLOPT_RETURNTRANSFER => true
]);
$response = json_decode(curl_exec($ch), true);
$payment_url = $response['payment_url'];
```

**Python:**
```python
import hashlib, requests

def allpay_sign(data: dict, api_key: str) -> str:
    d = {k: v for k, v in data.items()
         if k != 'sign' and v not in ('', None)}
    chunks = []
    for key in sorted(d):
        val = d[key]
        if isinstance(val, list):
            for item in val:
                for k in sorted(item):
                    if item[k] not in ('', None):
                        chunks.append(str(item[k]))
        else: chunks.append(str(val))
    chunks.append(api_key)
    return hashlib.sha256(':'.join(chunks).encode()).hexdigest()

body = {
    'login': 'your_login',
    'order_id': 'ORDER-001',
    'items': [{'name': 'Product A', 'qty': '1',
               'price': '100', 'vat': '1'}],
    'currency': 'ILS',
    'webhook_url': 'https://yoursite.com/webhook'
}
body['sign'] = allpay_sign(body, 'YOUR_API_KEY')
res = requests.post('https://allpay.to/app/?show=getpayment&mode=api12', json=body)
payment_url = res.json()['payment_url']
```

**Token charge:**
```json
# cURL
curl https://allpay.to/app/?show=getpayment&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "ORDER-002",
    "items": [{"name": "Monthly CRM plan", "qty": "1", "price": "49.00", "vat": "1"}],
    "currency": "ILS",
    "allpay_token": "TOKEN_VALUE",
    "sign": "YOUR_COMPUTED_SIGN"
  }'

// Node.js
const body = {
  login: 'your_login',
  order_id: 'ORDER-002',
  items: [{ name: 'Monthly CRM plan', qty: '1', price: '49.00', vat: '1' }],
  currency: 'ILS',
  allpay_token: 'TOKEN_VALUE'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
const res = await (await fetch('https://allpay.to/app/?show=getpayment&mode=api12', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
})).json();
// success: { order_id, status: 1 }
// failure: { error_code, error_msg }

// PHP
$body = [
  'login'        => 'your_login',
  'order_id'     => 'ORDER-002',
  'items'        => [['name' => 'Monthly CRM plan', 'qty' => '1', 'price' => '49.00', 'vat' => '1']],
  'currency'     => 'ILS',
  'allpay_token' => 'TOKEN_VALUE'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=getpayment&mode=api12
// success: ['order_id' => ..., 'status' => 1]
// failure: ['error_code' => 12, 'error_msg' => '...']
```

## 3.2 Payment Webhook {#webhook}

Allpay sends a POST request with `Content-Type: application/json` and a JSON body to your `webhook_url` after **every** payment attempt — both successful and failed. Respond with HTTP 200 to acknowledge. Allpay makes up to 10 delivery attempts total — the first retry 1 minute after the initial failure, then with progressively increasing intervals, with the final attempt within 24 hours.

> ✅ Always verify the signature before processing. A payment is confirmed only when **status == 1**.

### Payload Fields

| Field | Type | Description |
| --- | --- | --- |
| order_id | string | Your order identifier |
| status | integer | 0 — unpaid / failed; 1 — successful |
| amount | number | Payment amount |
| currency | string | ILS / USD / EUR |
| inst | integer | Number of installment payments |
| card_mask | string | Masked card number, e.g. `465901******7049` |
| card_brand | string | Visa / Mastercard / AmEx / Diners |
| foreign_card | integer | 0 — local card (issued by an Israeli bank); 1 — foreign card |
| receipt | string | URL to the digital receipt. Generated only if an accounting service (EasyCount or Morning) is connected in your Allpay account. |
| client_name | string | Customer name |
| client_email | string | Customer email |
| client_phone | string | Customer phone |
| client_tehudat | string | Customer Social ID (if provided) |
| add_field_1 | string | Custom data from request (unchanged) |
| add_field_2 | string | Custom data from request (unchanged) |
| sign | string | SHA256 signature — verify using your API key |

**cURL:**
```bash
{
  "order_id":      "ORDER-001",
  "status":       1,
  "amount":       100.00,
  "currency":     "ILS",
  "inst":         1,
  "card_mask":    "465901******7049",
  "card_brand":   "Visa",
  "foreign_card": 0,
  "client_name":  "Joe Doe",
  "client_email": "joe@example.com",
  "add_field_1":  "your-data",
  "sign":         "abc123..."
}
```

**PHP:**
```php
$payload = json_decode(file_get_contents('php://input'), true);
$received_sign = $payload['sign'] ?? '';

// Verify signature first
$expected = allpay_sign($payload, 'YOUR_API_KEY');
if (!hash_equals($expected, $received_sign)) {
  http_response_code(400);
  exit;
}

// Fulfill order only on successful payment
if ((int)$payload['status'] === 1) {
  fulfill_order($payload['order_id'], $payload['amount']);
}

// Always acknowledge receipt — including failed payment attempts
http_response_code(200);
echo 'OK';
```

**Node.js:**
```javascript
app.post('/webhook', (req, res) => {
  const payload = req.body;
  const expected = allpaySign(payload, 'YOUR_API_KEY');

  // Use timing-safe comparison
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(payload.sign))) {
    return res.sendStatus(400);
  }

  if (payload.status === 1) {
    fulfillOrder(payload.order_id, payload.amount);
  }

  // Always respond 200 — including failed payment attempts
  res.sendStatus(200);
});
```

## 3.3 Check Payment Status {#payment-status}

`POST https://allpay.to/app/?show=paymentstatus&mode=api12`

Check the status of a payment. Call at least 2 seconds after the payment attempt.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login |
| order_id* | string | required | Order identifier |
| sign* | string | required | SHA256 signature |

### Response

| Field | Type | Description |
| --- | --- | --- |
| order_id | string | Order identifier |
| status | integer | 0 — unpaid / not yet completed (payment attempt may have failed; customer can still retry); 1 — successful; 3 — refunded; 4 — partially refunded |
| amount | number | Payment amount |
| currency | string | Billing currency |
| inst | integer | Installment count |
| card_mask | string | Masked card number |
| card_brand | string | Card brand |
| foreign_card | integer | 0 — local card (issued by an Israeli bank); 1 — foreign card |
| receipt | string | URL to the digital receipt. Generated only if an accounting service (EasyCount or Morning) is connected in your Allpay account. |
| client_name / email / phone / tehudat | string | Customer details |

**Response:**
```json
{
  "order_id":      "ORDER-001",
  "status":       1,
  "amount":       100.00,
  "inst":         1,
  "currency":     "ILS",
  "foreign_card": 0,
  "card_mask":    "465901******7049",
  "card_brand":   "Visa",
  "receipt":      "",
  "client_name":  "Joe Doe",
  "client_email": "joe@example.com",
  "client_phone": "+972501234567",
  "client_tehudat": "123456789"
}
```

**cURL:**
```bash
curl https://allpay.to/app/?show=paymentstatus&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "ORDER-001",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login'    => 'your_login',
  'order_id' => 'ORDER-001'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=paymentstatus&mode=api12
// $response['status']: 0=unpaid, 1=paid, 3=refunded, 4=partial refund
```

**Node.js:**
```javascript
const body = {
  login: 'your_login', order_id: 'ORDER-001'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
const { status, amount, currency } = await (await fetch('https://allpay.to/app/?show=paymentstatus&mode=api12', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
})).json();
// status: 0=unpaid, 1=paid, 3=refunded, 4=partial refund
```

## 3.4 Refund Payment {#refund}

`POST https://allpay.to/app/?show=refund&mode=api12`

Issue a full or partial refund. Refunds are processed from your available withdrawal balance.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login |
| order_id* | string | required | Order identifier |
| amount* | string | required | Total refund amount as a string (e.g. `"100.00"`). Must be a string for correct signature calculation. |
| sign* | string | required | SHA256 signature |
| items | array | optional | For partial refund: array matching original items count/order. Each object has one field: `amount` (string). Use `"0"` to skip an item. Amounts must be strings. |

### Response

| Field | Type | Description |
| --- | --- | --- |
| order_id | string | Order identifier |
| msg | string | Human-readable result message, e.g. *The amount will be refunded to the customer's card within 7 business days.* |
| status | integer | 3 — fully refunded; 4 — partially refunded |
| receipt | string | Link to the refund receipt (negative receipt). Present only when refund documents were created — i.e. the original payment has a receipt issued via a connected accounting service (EasyCount or Morning). |
| receipt_tax | string | Link to the credit tax invoice. Present only for VAT-registered businesses, which receive two refund documents: a credit tax invoice and a negative receipt. |

**Response:**
```json
{
  "order_id":    "ORDER-001",
  "msg":         "The amount will be refunded to the customer's card within 7 business days.",
  "status":      3,
  "receipt":     "https://...",
  "receipt_tax": "https://..."
}

// receipt / receipt_tax are included only when refund documents
// were created (an accounting service — EasyCount or Morning — is connected).
// receipt_tax is issued only for VAT-registered businesses.
```

**cURL:**
```bash
# Full refund — omit items
curl https://allpay.to/app/?show=refund&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "ORDER-001",
    "amount": "100.00",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
// Partial refund: 3 original items, refund only 2nd
$body = [
  'login'    => 'your_login',
  'order_id' => 'ORDER-001',
  'amount'   => '200.00',
  'items'    => [
    ['amount' => '0'],    // item 1: skip
    ['amount' => '200'],  // item 2: refund 200
    ['amount' => '0'],    // item 3: skip
  ]
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=refund&mode=api12
```

**Node.js:**
```javascript
// Full refund
const body = {
  login: 'your_login', order_id: 'ORDER-001',
  amount: '100.00'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
const { status } = await (await fetch('https://allpay.to/app/?show=refund&mode=api12', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
})).json();
// status 3 = fully refunded, 4 = partially refunded
```

# 4. Subscriptions

Create and manage recurring billing. Currently, only monthly frequency is supported. Requires the Subscriptions module to be active on your account.

## 4.1 Create Subscription {#create-subscription}

`POST https://allpay.to/app/?show=getpayment&mode=api12`

Same as Create Payment but with an additional `subscription` object. Returns a `payment_url` for the customer to authorize the first charge. Subsequent charges happen automatically each month with webhooks sent for each. If the `subscription` object is omitted, the request is processed as a regular one-time payment.

> ⚠️ With `start_type=2` or `start_type=3`, the first charge is deferred, so the card is not sent to the issuing bank for authorization when the customer submits the payment page. This means an incorrect card number will pass this step and the subscription will be created successfully — the error will only surface later, when the first scheduled charge is attempted. To confirm the card is valid up front, use `start_type=1` so the first charge happens immediately on the payment page.

### subscription object

| Name | Type |  | Description |
| --- | --- | --- | --- |
| start_type* | integer | required | When first charge occurs:<br>1 — immediately (the customer is charged when they complete the payment page); 2 — specific date (start_date); 3 — after N days (start_n) |
| start_date | integer | optional | Unix timestamp (required if start_type=2) |
| start_n | integer | optional | Number of days (required if start_type=3) |
| end_type* | integer | required | When subscription ends:<br>1 — infinite; 2 — specific date (end_date); 3 — after N charges (end_n) |
| end_date | integer | optional | Unix timestamp (required if end_type=2) |
| end_n | integer | optional | Number of charges (required if end_type=3). Minimum 1: with `end_n=1` and `start_type=1` the customer is charged once on the payment page and the subscription completes immediately; with a deferred start the single charge occurs on the scheduled date. |

### Additional top-level parameter

| Name | Type |  | Description |
| --- | --- | --- | --- |
| button_title | string | optional | Button text. Default for subscriptions is `subscribe`.<br>subscribe; pay; donate |

**cURL:**
```bash
curl https://allpay.to/app/?show=getpayment&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "SUB-001",
    "items": [{"name": "Monthly Plan", "qty": "1", "price": "49", "vat": "1"}],
    "currency": "ILS",
    "webhook_url": "https://yoursite.com/webhook",
    "button_title": "subscribe",
    "subscription": {"start_type": 1, "end_type": 1},
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**Node.js:**
```javascript
const body = {
  login: 'your_login',
  order_id: 'SUB-001',
  items: [{ name: 'Monthly Plan', qty: '1', price: '49', vat: '1' }],
  currency: 'ILS',
  webhook_url: 'https://yoursite.com/webhook',
  button_title: 'subscribe',
  subscription: {
    start_type: 1,   // immediately
    end_type:   1    // infinite
  }
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
```

**PHP:**
```php
$body = [
  'login'    => 'your_login',
  'order_id' => 'SUB-001',
  'items'    => [['name' => 'Monthly Plan', 'qty' => '1', 'price' => '49', 'vat' => '1']],
  'currency'  => 'ILS',
  'webhook_url' => 'https://yoursite.com/webhook',
  'subscription' => [
    'start_type' => 3, 'start_n' => 7,  // first charge after 7 days
    'end_type'   => 3, 'end_n'   => 12  // cancel after 12 charges
  ]
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=getpayment&mode=api12
```

**Python:**
```python
body = {
    'login': 'your_login',
    'order_id': 'SUB-001',
    'items': [{'name': 'Monthly Plan', 'qty': '1',
               'price': '49', 'vat': '1'}],
    'currency': 'ILS',
    'subscription': {
        'start_type': '1', 'end_type': '1'
    }
}
body['sign'] = allpay_sign(body, 'YOUR_API_KEY')
# POST to https://allpay.to/app/?show=getpayment&mode=api12
```

## 4.2 Cancel Subscription {#cancel-subscription}

`POST https://allpay.to/app/?show=cancelsubscription&mode=api12`

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login |
| order_id* | string | required | Subscription order ID |
| sign* | string | required | SHA256 signature |

### Response

| Field | Type | Description |
| --- | --- | --- |
| status | integer | After cancellation you can expect 4 or 2 (subscription was already completed — no cancellation needed).<br>1 — active; 2 — completed; 3 — error; 4 — cancelled |

**cURL:**
```bash
curl https://allpay.to/app/?show=cancelsubscription&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "SUB-001",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login'    => 'your_login',
  'order_id' => 'SUB-001'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=cancelsubscription&mode=api12
// status 4 = cancelled, 2 = already completed
```

**Node.js:**
```javascript
const body = {
  login: 'your_login', order_id: 'SUB-001'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
const { status } = await (await fetch('https://allpay.to/app/?show=cancelsubscription&mode=api12', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
})).json();
// status 4 = cancelled, 2 = already completed
```

## 4.3 Subscription Status {#subscription-status}

`POST https://allpay.to/app/?show=subscriptionstatus&mode=api12`

Get detailed status and full charge history of a subscription.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login |
| order_id* | string | required | Subscription order ID |
| sign* | string | required | SHA256 signature |

### Response

| Field | Type | Description |
| --- | --- | --- |
| order_id | string | Subscription ID |
| status | integer | 1 — active; 2 — completed; 3 — error (last charge failed, retry tomorrow); 4 — cancelled |
| amount | number | Amount per charge |
| currency | string | ILS / USD / EUR |
| payments_n | integer | Number of successful charges |
| paid_total | number | Total amount charged |
| payments | array | Charge history: each item has `ts` (Unix timestamp), `amount`, `receipt` (URL) |

**Response:**
```json
{
  "order_id":    "SUB-001",
  "status":     1,
  "amount":     49.00,
  "currency":   "ILS",
  "payments_n": 3,
  "paid_total": 147.00,
  "payments": [
    { "ts": 1748131200, "amount": 49.00, "receipt": "" },
    { "ts": 1745539200, "amount": 49.00, "receipt": "" },
    { "ts": 1742947200, "amount": 49.00, "receipt": "" }
  ]
}
```

**cURL:**
```bash
curl https://allpay.to/app/?show=subscriptionstatus&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "SUB-001",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login'    => 'your_login',
  'order_id' => 'SUB-001'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=subscriptionstatus&mode=api12
// $response['status']: 1=active, 2=completed, 3=error, 4=cancelled
// $response['payments_n'] — successful charges count
// $response['paid_total'] — total charged amount
```

**Node.js:**
```javascript
const body = {
  login: 'your_login', order_id: 'SUB-001'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
const { status, payments_n, paid_total } =
  await (await fetch('https://allpay.to/app/?show=subscriptionstatus&mode=api12',
    { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) }
  )).json();
// status: 1=active, 2=completed, 3=error, 4=cancelled
```

## 4.4 List Subscriptions {#list-subscriptions}

`POST https://allpay.to/app/?show=getsubscriptions&mode=api12`

Returns a paginated list of subscriptions created via API under this API login (100 per page). Subscriptions created from the dashboard or under a different API login are not included.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login |
| sign* | string | required | SHA256 signature |
| status | integer | optional | Filter by subscription status. If omitted or set to 0, returns all subscriptions regardless of status.<br>0 — any; 1 — active; 2 — completed; 3 — error; 4 — cancelled |
| page | integer | optional | Page number to retrieve. If not provided, the first page is returned. Each page contains up to 100 subscriptions. |

### Response

| Field | Type | Description |
| --- | --- | --- |
| total_n | integer | Total subscriptions returned |
| next_page | integer | Next page number, or 0 if no more pages |
| subscriptions | array | Array of subscription objects.<br>order_id — string — Subscription order identifier<br>name — string — Subscription name (from the first item)<br>status — integer — 1 — active2 — completed3 — error4 — cancelled<br>amount — number — Amount per charge<br>currency — string — ILS / USD / EUR<br>payments_n — integer — Number of successful charges made<br>paid_total — number — Total amount charged to date<br>date_start — integer — Unix timestamp of the first charge<br>date_end — integer — Unix timestamp of the last charge; `0` for infinite subscriptions<br>next_payment — integer — Unix timestamp of the next scheduled charge<br>client_name — string — Customer name<br>client_email — string — Customer email<br>client_phone — string — Customer phone<br>client_tehudat — string — Customer Social ID (if provided)<br>add_field_1 — string — Custom data from the original request (if provided)<br>add_field_2 — string — Custom data from the original request (if provided) |

**Response:**
```json
{
  "total_n":  1,
  "next_page": 0,
  "subscriptions": [{
    "order_id":      "SUB-001",
    "name":         "Monthly Plan",
    "status":       1,
    "amount":       49.00,
    "currency":     "ILS",
    "payments_n":   3,
    "paid_total":   147.00,
    "date_start":   1742947200,
    "date_end":     0,  // 0 = infinite
    "next_payment": 1750723200,
    "client_name":  "Joe Doe",
    "client_email": "joe@example.com",
    "client_phone": "+972501234567",
    "client_tehudat": "123456789",
    "add_field_1":  "custom-data",
    "add_field_2":  ""
  }]
}
```

**cURL:**
```bash
curl https://allpay.to/app/?show=getsubscriptions&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "status": "1",
    "page": "1",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login'  => 'your_login',
  'status' => '1', // active only
  'page'   => '1'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=getsubscriptions&mode=api12
// $response['subscriptions'] — array of subscriptions
// $response['next_page'] — 0 if no more pages
```

**Node.js:**
```javascript
const body = {
  login: 'your_login', status: '1', page: '1'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
const { subscriptions, total_n, next_page } = await (await fetch('https://allpay.to/app/?show=getsubscriptions&mode=api12', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
})).json();
```

## 4.5 Subscription Statistics {#subscriptions-stats}

`POST https://allpay.to/app/?show=subscriptionsinfo&mode=api12`

Statistical breakdown of subscriptions grouped by status.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login |
| sign* | string | required | SHA256 signature |

### Response

`info` — array, one entry per status group:

| Field | Type | Description |
| --- | --- | --- |
| status | integer | 1 — active; 2 — completed; 3 — error; 4 — cancelled |
| total_n | integer | Number of subscriptions with this status |
| total_amount | number | Sum of all subscription amounts for this status group |

**Response:**
```json
{
  "info": [
    { "status": 1, "total_n": 42, "total_amount": 2058.00 },
    { "status": 2, "total_n":  5, "total_amount":  245.00 },
    { "status": 4, "total_n":  7, "total_amount":  343.00 }
  ]
}
```

**cURL:**
```bash
curl https://allpay.to/app/?show=subscriptionsinfo&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login' => 'your_login'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=subscriptionsinfo&mode=api12
// $response['info'][0] = ['status' => 1, 'total_n' => 42, 'total_amount' => 2058.00]
```

**Node.js:**
```javascript
const body = { login: 'your_login' };
body.sign = allpaySign(body, 'YOUR_API_KEY');
const { info } = await (await fetch('https://allpay.to/app/?show=subscriptionsinfo&mode=api12', {
  method: 'POST', headers: {'Content-Type': 'application/json'},
  body: JSON.stringify(body)
})).json();
// info[0] = { status: 1, total_n: 42, total_amount: 2058.00 }
```

# 5. Tokens

Save a card without charging, then reuse it for future payments without requiring the customer to re-enter card details.

We recommend using the token API endpoints when building billing for SaaS products. For SaaS services, Allpay's token API endpoints provide more flexible billing management than the Subscriptions API. Once a card token has been obtained, your system can submit it via the API for each new payment while independently determining the amount, date, and conditions of the charge. This allows you to change pricing plans and implement custom recurring payment scenarios that match your product's business model.

## 5.1 Capture Token Without Payment {#capture-token}

`POST https://allpay.to/app/?show=capturetoken&mode=api12`

Creates a card capture session. Returns a `payment_url` where the customer enters their card details — no charge is made. In case of a successful card capture, Allpay sends a POST webhook to your `webhook_url`. The webhook includes the `allpay_token`, which can be used to initiate a new payment or a subscription.

> ⚠️ Since no charge is made, the card is never sent to the issuing bank for authorization, so this endpoint cannot verify that the card is valid. A token will be created even for an incorrect card number, and the error will only surface later, when the first real charge against that token is attempted. To confirm a card is valid before saving it for future use, charge it via Create Payment and then retrieve the token for that payment with Get Token.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login |
| order_id* | string | required | Unique identifier in your system. Use a unique `order_id` for each capture session — do not reuse an `order_id` from a previous capture. |
| items* | array | required | Items for display only (no charge). Only `name` field is required per item. |
| sign* | string | required | SHA256 signature |
| button_title | string | optional | Submit button text.<br>submit; save; subscribe; pay; donate |
| lang | string | optional | Page language (AUTO, EN, HE, RU, AR, ES, IT, DE, FR) |
| webhook_url | string | optional | Allpay sends a POST webhook with card details, capture status, and `allpay_token` to this URL. If not provided, the token will only be visible in your Allpay dashboard. |
| success_url | string | optional | The customer is redirected to this URL after the token is captured. If not provided, the customer is redirected to the default Allpay success page. |
| backlink_url | string | optional | URL for the "Return to site" button displayed at the bottom of the page. Note: there is no fail URL — errors are displayed directly on the page, prompting the customer to try again. |
| client_name / email / phone / tehudat | string | optional | Prefill customer fields |
| add_field_1 / add_field_2 | string | optional | Custom data — returned in webhook |
| expire | integer | optional | Unix timestamp when link expires (default: 1 week) |

### API Response

|  |  |  |
| --- | --- | --- |
| payment_url | string | Redirect the customer here |

### Webhook (after customer submits card)

Success (`status = 1`):

| Field | Type | Description |
| --- | --- | --- |
| order_id | string | Your order identifier |
| status | integer | 1 — token captured successfully |
| allpay_token | string | Token representing the customer's card — use in future Create Payment calls or Create Subscription |
| items | array | Items from the request |
| card_mask | string | Masked card number, e.g. `465901******7049` |
| card_brand | string | Visa / Mastercard / AmEx / Diners |
| foreign_card | integer | 0 — local card (issued by an Israeli bank); 1 — foreign card |
| client_name | string | Customer name (if provided in request) |
| client_email | string | Customer email (if provided in request) |
| client_phone | string | Customer phone (if provided in request) |
| client_tehudat | string | Customer Social ID (if provided in request) |
| sign | string | SHA256 signature — verify using your API key |

Failed (`status = 0`):

| Field | Type | Description |
| --- | --- | --- |
| order_id | string | Your order identifier |
| status | integer | 0 — capture failed |
| error | string | Error description from the processor |
| sign | string | SHA256 signature — verify using your API key |

**Webhook:**
```text
// success (status = 1)
{
  "order_id":      "TOKEN-001",
  "status":        1,
  "allpay_token":  "6A0874314DCA42-79412113",
  "items":         [{ "name": "Save payment method" }],
  "card_mask":     "465901******7049",
  "card_brand":   "Visa",
  "foreign_card": 0,
  "client_name":  "Joe Doe",
  "client_email": "joe@example.com",
  "sign":         "abc123..."
}

// failed (status = 0)
{
  "order_id": "TOKEN-001",
  "status":   0,
  "error":    "Card declined",
  "sign":     "abc123..."
}
```

**cURL:**
```bash
curl https://allpay.to/app/?show=capturetoken&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "TOKEN-001",
    "items": [{"name": "Save payment method"}],
    "button_title": "save",
    "webhook_url": "https://yoursite.com/token-webhook",
    "lang": "EN",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login'       => 'your_login',
  'order_id'    => 'TOKEN-001',
  'items'       => [['name' => 'Save payment method']],
  'button_title' => 'save',
  'webhook_url' => 'https://yoursite.com/token-webhook'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=capturetoken&mode=api12
// redirect to $response['payment_url']
// webhook: { status:1, order_id:'TOKEN-001', allpay_token:'6A0874314DCA42-...', card_mask:'465901******7049', card_brand:'Visa', ... }
```

**Node.js:**
```javascript
const body = {
  login: 'your_login',
  order_id: 'TOKEN-001',
  items: [{ name: 'Save payment method' }],
  button_title: 'save',
  webhook_url: 'https://yoursite.com/token-webhook',
  lang: 'EN'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
const { payment_url } = await (await fetch('https://allpay.to/app/?show=capturetoken&mode=api12', {
  method: 'POST', headers: {'Content-Type': 'application/json'},
  body: JSON.stringify(body)
})).json();
// redirect customer to payment_url
// webhook: { status: 1, order_id: 'TOKEN-001', allpay_token: '6A0874314DCA42-...', card_mask: '465901******7049', card_brand: 'Visa', ... }
```

## 5.2 Get Token for Existing Payment {#get-token}

`POST https://allpay.to/app/?show=gettoken&mode=api12`

Retrieve a reusable token from a completed payment.

> ℹ️ Bit payments do not support tokenization.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login |
| order_id* | string | required | Order ID of the completed payment |
| sign* | string | required | SHA256 signature |

### Response

| Field | Type | Description |
| --- | --- | --- |
| order_id | string | Order identifier |
| allpay_token | string | Token for future payments |
| card_mask | string | Masked card number |
| card_brand | string | Card brand |
| foreign_card | integer | 0 — local card (issued by an Israeli bank); 1 — foreign card |

**cURL:**
```bash
curl https://allpay.to/app/?show=gettoken&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "ORDER-001",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login'    => 'your_login',
  'order_id' => 'ORDER-001'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=gettoken&mode=api12
// $response['allpay_token'] — use in future getpayment calls
```

**Node.js:**
```javascript
const body = {
  login: 'your_login', order_id: 'ORDER-001'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
const { allpay_token, card_mask } = await (await fetch('https://allpay.to/app/?show=gettoken&mode=api12', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
})).json();
// pass allpay_token in future Create Payment calls
```

# 6. J5 / Pre-auth

Reserve an amount on a card without charging it. The reservation is valid for 7 days (168 hours). You can charge an amount equal to or less than the reserved amount — but only once. To void a reservation without charging, call the Refund endpoint.

## 6.1 Pre-authorize (Reserve) {#preauthorize}

`POST https://allpay.to/app/?show=getpayment&mode=api12`

Same as Create Payment with `preauthorize: true`. Reserves the amount on the customer's card — no charge is made. Returns `payment_url`.

> ⚠️ The reservation expires after 168 hours (7 days). Charge or void it before then.

|  |  |  |
| --- | --- | --- |
| preauthorize | boolean | Set to `true` |

**cURL:**
```bash
curl https://allpay.to/app/?show=getpayment&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "J5-001",
    "items": [{"name": "Hotel reservation", "qty": "1", "price": "500", "vat": "1"}],
    "preauthorize": true,
    "webhook_url": "https://yoursite.com/webhook",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login'        => 'your_login',
  'order_id'     => 'J5-001',
  'items'        => [['name' => 'Hotel reservation', 'qty' => '1', 'price' => '500', 'vat' => '1']],
  'preauthorize' => true,
  'webhook_url'  => 'https://yoursite.com/webhook'
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=getpayment&mode=api12
// redirect to $response['payment_url']
```

**Node.js:**
```javascript
const body = {
  login: 'your_login', order_id: 'J5-001',
  items: [{ name: 'Hotel reservation', qty: '1', price: '500', vat: '1' }],
  preauthorize: true,
  webhook_url: 'https://yoursite.com/webhook'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
```

## 6.2 Charge Pre-authorized Payment {#charge-j5}

`POST https://allpay.to/app/?show=runauthorizedpayment&mode=api12`

Charge a reserved amount. Can only be done once, must not exceed the reserved amount, and must happen within 168 hours.

> ℹ️ To void a reservation without charging, call Refund without an `items` field.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login |
| order_id* | string | required | Order ID from the original pre-authorization |
| amount* | number | required | Amount to charge (≤ reserved amount) |
| sign* | string | required | SHA256 signature |

### Response

| Field | Type | Description |
| --- | --- | --- |
| order_id | string | Order identifier |
| status | integer | 0 — failed; 1 — successful |
| amount | number | Amount charged |

**cURL:**
```bash
curl https://allpay.to/app/?show=runauthorizedpayment&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "your_login",
    "order_id": "J5-001",
    "amount": 450.00,
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login'    => 'your_login',
  'order_id' => 'J5-001',
  'amount'   => 450.00  // charge less than reserved
];
$body['sign'] = allpay_sign($body, 'YOUR_API_KEY');
// POST to https://allpay.to/app/?show=runauthorizedpayment&mode=api12
```

**Node.js:**
```javascript
const body = {
  login: 'your_login', order_id: 'J5-001',
  amount: '450.00'
};
body.sign = allpaySign(body, 'YOUR_API_KEY');
const { status } = await (await fetch('https://allpay.to/app/?show=runauthorizedpayment&mode=api12', {
  method: 'POST', headers: {'Content-Type': 'application/json'},
  body: JSON.stringify(body)
})).json();
```

# 7. Utility

## 7.1 Verify API Credentials {#verify-keys}

`POST https://allpay.to/app/?show=checkkeys&mode=api12`

Verify that an API login + key pair is valid, without making a payment. Useful for onboarding flows where you're connecting a merchant's account.

### Parameters

| Name | Type |  | Description |
| --- | --- | --- | --- |
| login* | string | required | API login to verify |
| sign* | string | required | SHA256 signature generated with the API key |

### Response (valid credentials)

| Field | Type | Description |
| --- | --- | --- |
| last_paid_order_id | string | Last paid order ID, or `"-1"` if none |
| last_paid_order_date | string | Unix timestamp of last payment, or `"-1"` if none |

### Response (invalid credentials)

```
{ "error_code": 3, "error_msg": "Signature is incorrect" }
```

**cURL:**
```bash
curl https://allpay.to/app/?show=checkkeys&mode=api12 \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "login": "their_login",
    "sign": "YOUR_COMPUTED_SIGN"
  }'
```

**PHP:**
```php
$body = [
  'login' => 'their_login'
];
$body['sign'] = allpay_sign($body, 'THEIR_API_KEY');
// POST to https://allpay.to/app/?show=checkkeys&mode=api12
// isset($response['error_code']) → invalid credentials
```

**Node.js:**
```javascript
const body = { login: 'their_login' };
body.sign = allpaySign(body, 'THEIR_API_KEY');
const res = await (await fetch('https://allpay.to/app/?show=checkkeys&mode=api12', {
  method: 'POST', headers: {'Content-Type': 'application/json'},
  body: JSON.stringify(body)
})).json();

if (res.error_code) {
  // invalid credentials
} else {
  // valid — res.last_paid_order_id
}
```

## 7.2 Test Cards {#test-cards}

Enable test mode in your account settings, then use the card numbers below. Use any future date as the expiration date and any three digits for the CVV.

### Card Numbers

| Brand | Number | Result |
| --- | --- | --- |
| Visa | 4557430402053431 | ✓ Success |
| Mastercard | 5326105300985846 | ✓ Success |
| American Express | 375516193000090 | ✓ Success |
| Any brand | 4000000000000002 | ✗ Failure simulation |

> ℹ️ Test mode is enabled in settings of each integration. Real cards are not charged in test mode.

## 7.3 Changelog {#changelog}

API updates, new endpoints and parameters, and breaking changes — in reverse chronological order.

**July 17, 2026 v12**

Fixed subscription completion for `end_type=3` with `end_n=1`: previously, when the first charge occurred immediately (`start_type=1`), the subscription stayed active and a second charge was attempted a month later. Now the subscription completes right after the first charge. `end_n=1` is fully supported for all start types.

**July 6, 2026 v12**

The Refund Payment response now includes links to refund documents when they are created (an accounting service — EasyCount or Morning — is connected and the original payment has a receipt).

**receipt**

Link to the refund receipt (negative receipt).

**receipt_tax**

Link to the credit tax invoice. Issued only for VAT-registered businesses, which receive two refund documents.

**May 25, 2026 v12**

Added new error code `21` — *Amount must be at least 5 ILS*. Returned when the payment amount (after applying currency rate) is below 5 ILS. Previously this case was not enforced via the API.

**May 16, 2026 v12**

API v12 released: support for donation receipts and donation payment buttons.

In light of the new reporting requirements for non-profit organizations in Israel, Allpay API v12 now supports two new parameters for payment creation.

**doc_type**

Document type for the document issued after payment. If not provided, the default value is taken from your account settings.

Available values: `320` — Tax Invoice Receipt, `400` — Receipt, `405` — Receipt for donation.

**button_title**

Text displayed on the payment button. Available values: `pay` (default), `donate`, `subscribe`.

This update allows non-profit organizations to create payments that issue the correct donation document and display a more suitable payment button for donation flows.

**April 10, 2026 v11**

API v11 released. We've introduced several updates to improve flexibility and control over payments and card handling.

**New capturetoken endpoint**

Allows saving a customer's card without charging it. Works with both redirect flow and [Hosted Fields](https://www.allpay.co.il/help/hosted-fields).

**Webhook error notifications**

Webhook notifications are now sent to `webhook_url` in case of payment errors. The customer remains on the payment page, allowing them to retry with another card.

**Webhook delivery and retries**

Your server must return an HTTP 200 OK response to confirm successful receipt of a webhook. If any other status is returned, or the request fails due to a timeout or network error, Allpay will automatically retry delivery.

Allpay performs up to 10 delivery attempts in total. The first retry is made 1 minute after the initial failure. Subsequent retries are sent with progressively increasing intervals, with the final attempt occurring within 24 hours of the original request. If all delivery attempts fail, the webhook will be marked as failed and no further retries will be made.

**Custom payment button text**

You can now customize the payment button label using the new `button_title` parameter. Available options: "Pay" or "Donate".

**January 22, 2026**

The **notifications_url** parameter has been renamed to **webhook_url**. **notifications_url** remains valid and fully supported for all API versions below v10.

**January 16, 2026 v10**

We've released API v10, introducing the new `currency_display` parameter. This parameter allows you to display prices to customers in one currency while charging in the billing `currency`. It helps show prices in a familiar currency for customers, while keeping settlement and payouts in the merchant's preferred currency.

No changes are required if you do not need multi-currency price display.

**November 20, 2025 v9**

API version 9 introduces support for item-level discounts. Two new optional parameters have been added: `discount_val` — discount amount for the item, and `discount_type` — defines whether the discount is a fixed amount or a percentage. These parameters allow displaying the discount directly on the payment page and automatically deducting it from the item price.

**August 25, 2025**

Added new value `4 — partially refunded` to the payment status parameter in responses to Refund requests and Payment status verification requests.

**July 25, 2025**

Added **API Keys Verification** endpoint that allows developers of various platforms integrating with Allpay to verify the validity and authenticity of a user's Allpay API login and key without initiating a payment.

**July 15, 2025**

Added support for partial refunds by introducing the optional `items` array in the refund request.

**May 8, 2025**

**Introducing New J5 Transaction Flow.** J5 is a two-step payment process used in Israeli payment systems. It begins with a pre-authorization (reservation) of funds on a customer's card for up to 168 hours (7 days). During this time, you can charge the reserved amount — fully or partially. If no charge is made, the funds are automatically released.

This is applicable for use cases like deliveries, rentals, variable-weight goods, or custom orders. [Read more about J5 →](https://www.allpay.co.il/help/j5-transaction)

**March 12, 2025**

Allpay introduces **Hosted Fields** — a secure way to embed a payment form on a website or in an application, fully adapting it to your design. [Tutorial →](https://www.allpay.co.il/help/hosted-fields)

**February 13, 2025**

Added a new parameter `show_applepay` to the payment request, allowing control over the Apple Pay button visibility on the payment page. The Apple Pay module must be activated in your account first. This parameter is useful in order to hide the button when creating a card token, as Apple Pay payments cannot be used for tokenization.

**January 29, 2025**

We're introducing two new tools for developers to test payments via API:

**Allpay API Tester**

With the [Allpay API Tester](https://allpay.to/demo/test-api.php), you can send requests for new payments, refunds, subscriptions, and other operations in both live and test modes, simulating requests from your server.

**API Tag**

Every transaction processed via the API is marked with an "API" tag in the Allpay dashboard. Clicking on the tag allows you to view the associated API request and response.

**November 22, 2024**

Language support updates. New `lang` parameter values:

`AUTO` — automatically sets the payment page language based on the client's browser settings. This is now the default value.

`AR` — added support for Arabic language.

If the `lang` parameter is not provided or set to `AUTO`, the payment page will automatically display in the client's browser language. Providing `EN`, `RU`, `HE`, or `AR` will display the payment page in that language for all clients, regardless of their browser settings.

A language switcher is now available on the payment page, allowing clients to change the language at any time, regardless of the initial `lang` parameter setting.

**November 15, 2024 v6**

API updated to version 6.

**New parameters introduced**

`items` — an array containing product details, including names, quantities, prices, and VAT attributes. This information will appear in the Allpay app and in the digital invoice if digital invoice integration is enabled.

`expire` — a Unix timestamp that defines the lifetime of the payment link. Once the link expires, it becomes invalid for payment. This helps avoid situations where customers pay for products or services that are no longer available.

**Removed parameters**

`name` (product name) and `amount` (total payment amount).

**Key changes**

The `items` array replaces the need for the `name` and `amount` parameters. The final amount is calculated based on the prices and quantities provided in the `items` array. Using the `vat` parameter inside the `items` array, Allpay will either display the VAT amount on the payment page or indicate that VAT is not included.

**Important:** Prices provided in the `items` array must already include VAT (if applicable). The `vat` parameter is used only to specify whether VAT is included in the item's price or not. We do not add VAT on top of the prices.

The old API version will continue to function as before.

**October 6, 2024**

Added support for full or partial refunds via the API. See the Refund endpoint.

**August 3, 2024**

The new payment request parameter `show_bit` allows you to enable or disable the display of the Bit payment button on the payment page. The Bit module must be activated in your Allpay account first.

**March 14, 2024**

The **receipt** parameter is included in both payment notification and payment verification response. This parameter provides the URL to the digital receipt, which is generated by the EasyCount module when the module is activated in the account settings.

Please note that the request URL changed to `...api4`.

**February 9, 2024**

Added new optional parameter for Payment Request: **client_tehudat**, representing the client's Social ID Number (Teudat Zehut). If provided, Allpay won't prompt the client for manual entry. If not provided, it will be requested on the payment page, as required by law. For non-Israeli citizens, submit `000000000`.

**December 24, 2023**

**fail_url** parameter will no longer be applied because payment errors are displayed directly on the payment page, prompting the customer to make a new payment attempt.

New parameter added: **backlink_url** — a URL for the new "Return to site" button on the bottom of the payment page.

**December 21, 2023**

New parameters added in the responses for payment protocol, status verification and token requests: **card_mask** (example: `465901******7049`), **card_brand** (example: Visa, Mastercard etc.) and **foreign_card** (issued in Israel or abroad).

Request URLs changed from `...api1` to `...api2`.

**September 9, 2023**

Added endpoint for creating and using tokens.

**June 30, 2023**

When submitting the `currency` parameter in USD or EUR, the amount will be auto-converted to ILS on the Allpay side. Exchange rates are taken in real time from Google Finance.

**June 29, 2023**

Added payment verification method to check transaction status.

## 7.4 Resources & Support {#resources}

Tools, guides, and contact information to help with your integration.

### Tools

|  |  |
| --- | --- |
| [API Tester](https://allpay.to/demo/test-api.php) | Interactive tool to send API requests and inspect responses without writing code |
| [API Reference for AI models (.md)](https://allpay.to/docs/allpay_api_reference_llm.md) | Machine-readable Markdown version of this reference — feed it to an AI coding assistant (Copilot, Claude, Codex, etc.) to help generate an accurate integration |
| [Hosted Fields](https://www.allpay.co.il/help/hosted-fields) | Embed a PCI-compliant card input directly in your page without redirecting the customer |

### Guides

|  |  |
| --- | --- |
| [Webhooks Guide](https://www.allpay.co.il/help/webhooks) | How to configure per-integration webhook URLs — in addition to the `webhook_url` passed per request, you can set a fixed URL in your integration settings that Allpay will also notify after every payment |
| [API Q&A](https://www.allpay.co.il/help/api-q-and-a) | Answers to the most common integration questions |
| [Currencies List](https://www.allpay.co.il/help/currencies-list-and-api-requests) | All supported currencies and their codes for `currency` / `currency_display` |
| [J5 / Pre-auth Guide](https://www.allpay.co.il/help/j5-transaction) | In-depth explanation of the two-step pre-authorization flow |
| [Token UI Recommendations](https://www.allpay.co.il/en/help/recommendations-to-build-interface-for-tokens) | UX guidelines for building a saved-card interface using tokens |

### Support & Updates

|  |  |
| --- | --- |
| [support@allpay.co.il](mailto:support@allpay.co.il) | Technical support over email |
| [@allpay_israel](https://t.me/allpay_israel) | Technical support over Telegram |
| [@allpay_api](https://t.me/allpay_api) | Telegram channel to track API updates |
