> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waypay.live/llms.txt
> Use this file to discover all available pages before exploring further.

# Initiate Checkout

> Create a payment checkout session for customers to complete their payment

## Overview

This endpoint creates a payment intent and returns a checkout URL where customers can complete their payment. The checkout session supports multiple payment methods including cards and mobile wallets.

## Path Parameters

<ParamField path="version" type="string" required>
  API version (e.g., "1")
</ParamField>

## Request Body

<ParamField body="amount" type="number" required>
  Payment amount (must be greater than 10)
</ParamField>

<ParamField body="currency" type="string">
  Payment currency (e.g., "PKR", "USD")
</ParamField>

<ParamField body="description" type="string" required>
  Payment description (1-200 alphanumeric characters and spaces only)

  **Pattern:** `^[A-Za-z0-9 ]{1,200}$`
</ParamField>

<ParamField body="customerRef" type="object">
  Customer information (optional object, all fields within are also optional)

  <Expandable title="properties">
    <ParamField body="name" type="string">
      Customer full name
    </ParamField>

    <ParamField body="email" type="string">
      Customer email address (must be valid email format when provided)
    </ParamField>

    <ParamField body="cnic" type="string">
      Customer CNIC number - must be exactly 13 digits when provided

      **Pattern:** `^\d{13}$`

      **Example:** `3520108345678`
    </ParamField>

    <ParamField body="phone" type="string">
      Customer phone number
    </ParamField>

    <ParamField body="shippingAddress" type="string">
      Optional shipping address
    </ParamField>

    <ParamField body="estimatedDays" type="string">
      Estimated delivery days
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="orderRef" type="object" required>
  Order information

  <Expandable title="properties">
    <ParamField body="orderRef" type="string" required>
      Order reference (alphanumeric characters)

      **Pattern:** `^[A-Za-z0-9]+$`
    </ParamField>

    <ParamField body="discount" type="string">
      Discount amount or percentage
    </ParamField>

    <ParamField body="tax" type="string">
      Tax amount or percentage
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="callbackUrl" type="string">
  Webhook URL on your server to receive real-time transaction status updates

  This should be a publicly accessible HTTPS endpoint in your merchant system that can receive POST requests from Waypay. When a transaction status changes, Waypay will send a webhook notification to this URL with the transaction details.

  **When to use this parameter:**

  * Use this parameter only if you need a **dynamic webhook URL** that varies per transaction (e.g., session-specific, order-specific URLs)
  * If your webhook URL is **static** (same for all transactions), configure it in your **Merchant Portal** instead. The system will automatically use the portal-configured URL when this parameter is not provided.

  **Maximum length:** 2048 characters

  **Required:** HTTPS URL that can accept POST requests

  **Example (Dynamic):** `https://yoursite.com/api/webhooks/payment?order_id=12345`

  **Example (Static):** Configure `https://yoursite.com/api/webhooks/payment` in Merchant Portal and omit this parameter

  <Tip>
    **Best Practice:** Use the Merchant Portal for static webhook URLs to avoid sending the same URL with every API request. Use this parameter only when you need dynamic, per-transaction webhook URLs.
  </Tip>

  **Webhook Payload Example:**

  ```json theme={null}
  {
    "id": "36e98f20908e426fa8a2b67fdfb088f0",
    "type": "TransactionStatusChanged",
    "created": 1768724277,
    "livemode": false,
    "data": {
      "TransactionId": "829727d1-7193-4177-9da8-9765b18699c3",
      "PreviousStatus": "Pending",
      "CurrentStatus": "Completed",
      "Amount": 20.00,
      "Currency": "PKR",
      "Reason": null,
      "TransactionReference": "WPAYTREA51GR85549B81",
      "CustomerRef": null,
      "OrderRef": "{\"OrderRef\":\"Abcd12456\",\"Discount\":null,\"Tax\":null}",
      "MerchantId": "dd15c78a-83b0-499f-9e6c-beba37a8f875",
      "Timestamp": "2026-01-18T08:17:57.6448041+00:00"
    },
    "api_version": "v1"
  }
  ```
</ParamField>

<ParamField body="intentType" type="string">
  Payment intent type (e.g., "checkout")
</ParamField>

<ParamField body="paymentMethod" type="integer">
  Preferred payment method:

  * 1: JazzCash
  * 2: Easypaisa

  If not specified, customer can choose from all available payment methods
</ParamField>

<ParamField body="metadataJson" type="string">
  Optional JSON string containing additional metadata for the transaction
</ParamField>

<ParamField body="signature" type="string" required>
  Request signature for security verification

  A cryptographic signature generated using MD5 hash algorithm to ensure the integrity and authenticity of the request. The signature is calculated using all request parameters (excluding the signature field itself) combined with your merchant secret key.

  **Format:** 32-character lowercase hexadecimal string

  **Example:** `a1b2c3d4e5f6789012345678abcdef12`

  <Warning>
    Never expose your secret key in client-side code. Always generate signatures on your server.
  </Warning>

  **Learn how to generate signatures:** See the complete [Signature Generation Guide](/signature-guide) for step-by-step instructions and implementation examples in C#, Node.js, Python, PHP, and Java.
</ParamField>

## Response

<ResponseField name="paymentIntentId" type="uuid">
  Unique identifier for the payment intent
</ResponseField>

<ResponseField name="checkoutUrl" type="string">
  URL where customer can complete the payment
</ResponseField>

<ResponseField name="expiresAt" type="datetime">
  Checkout session expiration timestamp
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://gateway.dev.waypay.live/Gateway/v1/Payment/initiate-checkout \
    --header 'SWICH-API-Key: pk_test_xxxxxxxx' \
    --header 'Content-Type: application/json' \
    --data '{
      "amount": 1000,
      "currency": "PKR",
      "description": "Product Purchase",
      "orderRef": {
        "orderRef": "ORD123456",
        "discount": "10",
        "tax": "15"
      },
      "callbackUrl": "https://yoursite.com/payment/callback",
      "intentType": "checkout",
      "paymentMethod": 1,
      "signature": "a1b2c3d4e5f6789012345678abcdef12"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    'https://gateway.dev.waypay.live/Gateway/v1/Payment/initiate-checkout',
    {
      method: 'POST',
      headers: {
        'SWICH-API-Key': 'pk_test_xxxxxxxx',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        amount: 1000,
        currency: 'PKR',
        description: 'Product Purchase',
        orderRef: {
          orderRef: 'ORD123456',
          discount: '10',
          tax: '15'
        },
        callbackUrl: 'https://yoursite.com/payment/callback',
        intentType: 'checkout',
        paymentMethod: 1,
        signature: 'a1b2c3d4e5f6789012345678abcdef12'
      })
    }
  );

  const data = await response.json();
  console.log('Checkout URL:', data.checkoutUrl);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://gateway.dev.waypay.live/Gateway/v1/Payment/initiate-checkout',
      headers={
          'SWICH-API-Key': 'pk_test_xxxxxxxx',
          'Content-Type': 'application/json'
      },
      json={
          'amount': 1000,
          'currency': 'PKR',
          'description': 'Product Purchase',
          'orderRef': {
              'orderRef': 'ORD123456',
              'discount': '10',
              'tax': '15'
          },
          'callbackUrl': 'https://yoursite.com/payment/callback',
          'intentType': 'checkout',
          'paymentMethod': 1,
          'signature': 'a1b2c3d4e5f6789012345678abcdef12'
      }
  )

  data = response.json()
  print(f"Payment Intent ID: {data['paymentIntentId']}")
  print(f"Checkout URL: {data['checkoutUrl']}")
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "paymentIntentId": "550e8400-e29b-41d4-a716-446655440000",
    "checkoutUrl": "https://checkout.waypay.com/pay/abc123xyz",
    "expiresAt": "2025-12-12T12:00:00Z"
  }
  ```

  ```json 400 Bad Request theme={null}
  {
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "Bad Request",
    "status": 400,
    "detail": "Invalid request parameters"
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "type": "https://tools.ietf.org/html/rfc7235#section-3.1",
    "title": "Unauthorized",
    "status": 401,
    "detail": "Invalid or missing API key"
  }
  ```

  ```json 422 Unprocessable Content theme={null}
  {
    "type": "https://tools.ietf.org/html/rfc4918#section-11.2",
    "title": "Unprocessable Content",
    "status": 422,
    "detail": "Validation failed for one or more fields"
  }
  ```
</ResponseExample>

## Validation Rules

### Amount (Required)

* Must be a number greater than 10
* Format: decimal/double

### Description (Required)

* Length: 1-200 characters
* Pattern: `^[A-Za-z0-9 ]{1,200}$`
* Only alphanumeric characters and spaces allowed

### CNIC (Optional)

* Must be exactly 13 digits when provided
* Pattern: `^\d{13}$`
* Example: `3520108345678`

### Email (Optional)

* Must be valid email format when provided
* Example: `user@example.com`

### Order Reference (Required)

* Alphanumeric characters only
* Pattern: `^[A-Za-z0-9]+$`
* Must be unique per transaction

## Required vs Optional Fields

### Required Fields

* `amount` - Payment amount
* `description` - Transaction description
* `orderRef` - Order information object
* `orderRef.orderRef` - Unique order reference
* `signature` - Request signature for security verification

### Optional Fields

* `currency` - Payment currency (defaults to merchant's currency)
* `customerRef` - Customer information object
* `customerRef.name` - Customer full name
* `customerRef.email` - Customer email address
* `customerRef.cnic` - Customer CNIC number
* `customerRef.phone` - Customer phone number
* `customerRef.shippingAddress` - Shipping address
* `customerRef.estimatedDays` - Estimated delivery days
* `orderRef.discount` - Discount information
* `orderRef.tax` - Tax information
* `callbackUrl` - Dynamic callback URL for post-payment redirect
* `intentType` - Payment intent type
* `paymentMethod` - Preferred payment method (1: JazzCash, 2: Easypaisa)
* `metadataJson` - Additional metadata

## Checkout Session Flow

1. **Create Checkout Session** - Call this endpoint with payment details
2. **Receive Checkout URL** - Get the `checkoutUrl` from the response
3. **Redirect Customer** - Send customer to the checkout URL
4. **Customer Pays** - Customer selects payment method and completes payment
5. **Receive Webhook** - You receive webhook notification of payment status
6. **Customer Redirected** - Customer is redirected to your `callbackUrl` with payment status

<Info>
  Checkout sessions expire after a certain period (indicated by `expiresAt`). After expiration, the customer must initiate a new checkout session.
</Info>

## Callback URL

The `callbackUrl` parameter specifies a webhook endpoint on your server where Waypay will send real-time transaction status updates.

### Static vs Dynamic Webhook URLs

**Static Webhook URL (Recommended for most use cases):**

* Configure once in your **Merchant Portal** under Webhook Settings
* The system automatically uses this URL for all transactions
* No need to include `callbackUrl` in API requests
* Easier to manage and update
* **Best for:** Standard integrations where all webhooks go to the same endpoint

**Dynamic Webhook URL (Optional):**

* Provide `callbackUrl` parameter in each API request
* Overrides the portal-configured URL for that specific transaction
* Useful for session-specific or order-specific routing
* **Best for:** Multi-tenant systems, session tracking, or custom routing needs

<Note>
  **Priority:** If both are configured, the `callbackUrl` parameter in the API request takes precedence over the Merchant Portal setting.
</Note>

### How It Works

* When a transaction status changes (e.g., Pending → Completed), Waypay sends a POST request to your callback URL
* Your webhook endpoint must accept POST requests and return a 200 OK response
* The webhook contains complete transaction details in JSON format
* This enables real-time order processing and updates in your system

### Webhook Payload Structure

**Headers:**

* `Content-Type: application/json`
* `User-Agent: Waypay-Webhook/1.0`

**Body:**

```json theme={null}
{
  "id": "36e98f20908e426fa8a2b67fdfb088f0",
  "type": "TransactionStatusChanged",
  "created": 1768724277,
  "livemode": false,
  "data": {
    "TransactionId": "829727d1-7193-4177-9da8-9765b18699c3",
    "PreviousStatus": "Pending",
    "CurrentStatus": "Completed",
    "Amount": 20.00,
    "Currency": "PKR",
    "Reason": null,
    "TransactionReference": "WPAYTREA51GR85549B81",
    "CustomerRef": null,
    "OrderRef": "{\"OrderRef\":\"Abcd12456\",\"Discount\":null,\"Tax\":null}",
    "MerchantId": "dd15c78a-83b0-499f-9e6c-beba37a8f875",
    "Timestamp": "2026-01-18T08:17:57.6448041+00:00"
  },
  "api_version": "v1"
}
```

### Webhook Fields

| Field                       | Type     | Description                                   |
| --------------------------- | -------- | --------------------------------------------- |
| `id`                        | string   | Unique webhook event ID                       |
| `type`                      | string   | Event type (e.g., "TransactionStatusChanged") |
| `created`                   | integer  | Unix timestamp when webhook was created       |
| `livemode`                  | boolean  | Whether this is a production transaction      |
| `data.TransactionId`        | uuid     | Unique transaction identifier                 |
| `data.PreviousStatus`       | string   | Previous transaction status                   |
| `data.CurrentStatus`        | string   | New transaction status                        |
| `data.Amount`               | decimal  | Transaction amount                            |
| `data.Currency`             | string   | Currency code (e.g., "PKR")                   |
| `data.TransactionReference` | string   | Payment provider reference number             |
| `data.OrderRef`             | string   | Your order reference (JSON string)            |
| `data.MerchantId`           | uuid     | Your merchant identifier                      |
| `data.Timestamp`            | datetime | When the status changed                       |

### Transaction Statuses

Common status transitions:

* `Pending` → `Completed` - Payment successful
* `Pending` → `Failed` - Payment failed
* `Pending` → `Cancelled` - Payment cancelled by customer
* `Pending` → `Expired` - Payment session expired

### Webhook Endpoint Requirements

Your webhook endpoint must:

1. **Accept POST requests** - Waypay sends webhooks via HTTP POST
2. **Use HTTPS** - Must be a secure endpoint (SSL/TLS required)
3. **Be publicly accessible** - Waypay servers must be able to reach your endpoint
4. **Respond quickly** - Return 200 OK within 5 seconds
5. **Handle idempotency** - Same webhook may be sent multiple times
6. **Validate authenticity** - Verify the webhook is from Waypay (recommended)

### Example Webhook Handler

<CodeGroup>
  ```javascript Node.js/Express theme={null}
  app.post('/api/webhooks/payment', express.json(), (req, res) => {
    const webhook = req.body;
    
    // Validate webhook
    if (webhook.type === 'TransactionStatusChanged') {
      const { TransactionId, CurrentStatus, Amount, OrderRef } = webhook.data;
      
      // Parse OrderRef JSON string
      const order = JSON.parse(OrderRef);
      
      // Process based on status
      if (CurrentStatus === 'Completed') {
        // Update order status in database
        // Send confirmation email
        // Fulfill order
        console.log(`Payment completed for order ${order.OrderRef}`);
      } else if (CurrentStatus === 'Failed') {
        // Handle failed payment
        console.log(`Payment failed for order ${order.OrderRef}`);
      }
    }
    
    // Always return 200 OK
    res.status(200).send('Webhook received');
  });
  ```

  ```python Python/Flask theme={null}
  @app.route('/api/webhooks/payment', methods=['POST'])
  def handle_payment_webhook():
      webhook = request.json
      
      # Validate webhook
      if webhook.get('type') == 'TransactionStatusChanged':
          data = webhook.get('data', {})
          transaction_id = data.get('TransactionId')
          current_status = data.get('CurrentStatus')
          amount = data.get('Amount')
          
          # Parse OrderRef JSON string
          order_ref = json.loads(data.get('OrderRef', '{}'))
          
          # Process based on status
          if current_status == 'Completed':
              # Update order status in database
              # Send confirmation email
              # Fulfill order
              print(f"Payment completed for order {order_ref.get('OrderRef')}")
          elif current_status == 'Failed':
              # Handle failed payment
              print(f"Payment failed for order {order_ref.get('OrderRef')}")
      
      # Always return 200 OK
      return '', 200
  ```

  ```php PHP theme={null}
  <?php
  // webhook.php
  $webhook = json_decode(file_get_contents('php://input'), true);

  if ($webhook['type'] === 'TransactionStatusChanged') {
      $data = $webhook['data'];
      $transactionId = $data['TransactionId'];
      $currentStatus = $data['CurrentStatus'];
      $amount = $data['Amount'];
      
      // Parse OrderRef JSON string
      $orderRef = json_decode($data['OrderRef'], true);
      
      // Process based on status
      if ($currentStatus === 'Completed') {
          // Update order status in database
          // Send confirmation email
          // Fulfill order
          error_log("Payment completed for order " . $orderRef['OrderRef']);
      } elseif ($currentStatus === 'Failed') {
          // Handle failed payment
          error_log("Payment failed for order " . $orderRef['OrderRef']);
      }
  }

  // Always return 200 OK
  http_response_code(200);
  echo 'Webhook received';
  ?>
  ```
</CodeGroup>

### Best Practices

<AccordionGroup>
  <Accordion title="Return 200 OK Immediately">
    Your webhook endpoint should return a 200 OK response as quickly as possible. Process the webhook data asynchronously if needed.
  </Accordion>

  <Accordion title="Handle Duplicate Webhooks">
    Use the webhook `id` or `TransactionId` to prevent processing the same event multiple times. Store processed webhook IDs in your database.
  </Accordion>

  <Accordion title="Verify Webhook Authenticity">
    Validate that webhooks are actually from Waypay by checking the source IP or implementing webhook signatures (contact support for details).
  </Accordion>

  <Accordion title="Log All Webhooks">
    Keep logs of all webhook events received for debugging and reconciliation purposes.
  </Accordion>

  <Accordion title="Parse OrderRef Carefully">
    The `OrderRef` field is a JSON string. Always parse it properly to extract your order reference and other data.
  </Accordion>

  <Accordion title="Handle All Status Types">
    Implement handlers for all possible transaction statuses: Completed, Failed, Cancelled, Expired, etc.
  </Accordion>
</AccordionGroup>

### Testing Webhooks

**In Test Mode:**

1. Use tools like [ngrok](https://ngrok.com) to expose your local development server
2. Set the ngrok URL as your `callbackUrl`
3. Monitor webhook deliveries in your application logs
4. Test with various payment scenarios (success, failure, cancellation)

**Example ngrok setup:**

```bash theme={null}
# Start your local server
npm start  # Running on localhost:3000

# In another terminal, start ngrok
ngrok http 3000

# Use the ngrok URL in your callback
# https://abc123.ngrok.io/api/webhooks/payment
```

## Best Practices

* **Provide Email**: Include customer email for payment receipts and notifications
* **Include Phone**: Provide phone number for OTP verification and updates
* **Set Webhook URL**: Configure a reliable `callbackUrl` webhook endpoint to receive real-time transaction updates
* **Implement Webhook Handler**: Build a robust webhook handler that processes all transaction statuses
* **Return 200 OK Fast**: Your webhook endpoint should respond within 5 seconds
* **Use Unique Order References**: Ensure `orderRef` is unique per transaction
* **Store Payment Intent ID**: Save the `paymentIntentId` for future reference
* **Monitor Expiration**: Track `expiresAt` and handle expired sessions
* **Process Webhooks Asynchronously**: Don't block webhook responses with long-running tasks
* **Add Metadata**: Use `metadataJson` for additional context (cart items, customer preferences, etc.)
* **Validate Before Submit**: Pre-validate all data before making the API call
* **Handle Errors Gracefully**: Implement proper error handling and user feedback

## Metadata Usage

The `metadataJson` field accepts a JSON string with additional information:

```json theme={null}
{
  "metadataJson": "{\"cart_items\": [{\"id\": \"PROD123\", \"qty\": 2}], \"promo_code\": \"SAVE10\", \"source\": \"mobile_app\"}"
}
```

This metadata is:

* Stored with the payment intent
* Included in webhook notifications
* Retrievable via transaction lookup
* Useful for reconciliation and analytics

## Security Considerations

* Use HTTPS for all callback URLs (webhook endpoints)
* Validate webhook authenticity to ensure requests are from Waypay
* Verify payment status via the Transaction API for critical operations
* Keep API keys secure and never expose in client-side code
* Implement CSRF protection on webhook endpoints
* Use webhook signatures for verification (contact support for implementation details)
* Store webhook IDs to prevent duplicate processing
* Sanitize and validate all webhook payload data
* Set appropriate expiration times for sessions
* Monitor for unusual patterns or abuse
* Log all webhook events for audit trail

## Testing

Use these test credentials in sandbox mode:

* **Name**: `Test User`
* **Email**: `test@example.com`
* **CNIC**: `3520108345678`
* **Phone**: `03123456789`
* **Amount**: Any amount > 10
* **Order Reference**: `TEST-ORD-XXXXX`

## Common Error Scenarios

| Error                | Cause                                          | Solution                                    |
| -------------------- | ---------------------------------------------- | ------------------------------------------- |
| 400 Bad Request      | Invalid field format or missing required field | Check all required fields and formats       |
| 401 Unauthorized     | Invalid API key                                | Verify API key is correct and active        |
| 422 Validation Error | Business logic validation failed               | Check amount, currency, duplicate order ref |
| Expired session      | Customer took too long to pay                  | Create new checkout session                 |

## Next Steps

<CardGroup cols={2}>
  <Card title="Setup Webhooks" icon="webhook" href="/guides/webhooks">
    Receive real-time payment notifications
  </Card>

  <Card title="Check Transaction Status" icon="magnifying-glass" href="/api-reference/endpoints/transaction-by-ref">
    Query payment status by reference
  </Card>

  <Card title="Handle Refunds" icon="rotate-left" href="/api-reference/endpoints/refunds-create">
    Process refunds for completed payments
  </Card>

  <Card title="Mobile Wallet Deposit" icon="mobile" href="/api-reference/endpoints/payment-deposit">
    Alternative: Direct mobile wallet charging
  </Card>
</CardGroup>
