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

# Create Session

> Create a payment session with amount, currency, and items. Supports public (pk_) and secret (sk_) API keys, optional IP validation for fraud prevention, direct provider redirect via paymentMethodId, and rate limiting

## Overview

Creates a new payment session for your customer. The session ID can be used to redirect the customer to the payment page or to fetch available payment methods.

<Note>
  Payment sessions expire after 24 hours if no payment is initiated.
</Note>

## Authentication

This endpoint accepts both **Public API Key** (client-side) and **Secret API Key** (server-side). See [Authentication](/api/authentication) for details.

```bash theme={null}
Authorization: Bearer YOUR_API_KEY
```

## Integration Modes

<Tabs>
  <Tab title="Client-side (SDK)">
    Use your **Public API Key** when creating sessions from the browser via the JavaScript SDK.

    * Client IP is **automatically captured** from the request
    * No need to provide the `ip` parameter
    * Ideal for: websites, single-page applications

    ```javascript theme={null}
    const payviox = new Payviox('pk_live_xxxxxxxxxxxx');
    await payviox.createSession({ ... });
    ```
  </Tab>

  <Tab title="Server-side (API)">
    Use your **Secret API Key** when creating sessions from your backend server.

    * You can **optionally provide** the client's IP address via the `ip` parameter
    * If `ip` is provided, it will be validated when the user completes payment
    * If `ip` is not provided, no IP validation will occur
    * Ideal for: backend APIs, mobile app backends, server integrations

    ```bash theme={null}
    curl https://api.payviox.com/session \
      -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
      -d '{ "ip": "203.0.113.42", ... }'
    ```

    <Warning>
      When you provide an `ip` parameter, the payment page will verify that the user's IP matches. If there's a mismatch, the payment will be rejected with a **403 IP mismatch** error.
    </Warning>
  </Tab>
</Tabs>

## Example Requests

### Client-side Example (Public API Key)

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.payviox.com/session \
    -H "Authorization: Bearer pk_live_xxxxxxxxxxxx" \
    -H "Content-Type: application/json" \
    -X POST \
    -d '{
      "amount": 10000,
      "currency": "USD",
      "customer": "customer_abc123",
      "order_id": "order_12345",
      "description": "Premium subscription",
      "metadata": {"user_id": "usr_123", "plan": "premium"},
      "items": [
        {
          "name": "Premium Plan",
          "quantity": 1,
          "price": 10000
        }
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.payviox.com/session', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer pk_live_xxxxxxxxxxxx',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      amount: 10000,
      currency: 'USD',
      customer: 'customer_abc123',
      order_id: 'order_12345',
      description: 'Premium subscription',
      metadata: { user_id: 'usr_123', plan: 'premium' },
      items: [
        {
          name: 'Premium Plan',
          quantity: 1,
          price: 10000
        }
      ]
    })
  });

  const data = await response.json();
  console.log(data.session_id);
  ```

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

  url = 'https://api.payviox.com/session'
  headers = {
      'Authorization': 'Bearer pk_live_xxxxxxxxxxxx',
      'Content-Type': 'application/json'
  }

  payload = {
      'amount': 10000,
      'currency': 'USD',
      'customer': 'customer_abc123',
      'order_id': 'order_12345',
      'description': 'Premium subscription',
      'metadata': {'user_id': 'usr_123', 'plan': 'premium'},
      'items': [
          {
              'name': 'Premium Plan',
              'quantity': 1,
              'price': 10000
          }
      ]
  }

  response = requests.post(url, headers=headers, json=payload)
  data = response.json()
  print(data['session_id'])
  ```

  ```php PHP theme={null}
  <?php

  $apiKey = 'pk_live_xxxxxxxxxxxx';
  $url = 'https://api.payviox.com/session';

  $data = [
      'amount' => 10000,
      'currency' => 'USD',
      'customer' => 'customer_abc123',
      'order_id' => 'order_12345',
      'description' => 'Premium subscription',
      'metadata' => ['user_id' => 'usr_123', 'plan' => 'premium'],
      'items' => [
          [
              'name' => 'Premium Plan',
              'quantity' => 1,
              'price' => 10000
          ]
      ]
  ];

  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'Authorization: Bearer ' . $apiKey,
      'Content-Type: application/json'
  ]);

  $response = curl_exec($ch);
  curl_close($ch);

  $result = json_decode($response, true);
  echo $result['session_id'];
  ```

  ```ruby Ruby theme={null}
  require 'net/http'
  require 'json'

  uri = URI('https://api.payviox.com/session')
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri)
  request['Authorization'] = 'Bearer pk_live_xxxxxxxxxxxx'
  request['Content-Type'] = 'application/json'

  request.body = {
    amount: 10000,
    currency: 'USD',
    customer: 'customer_abc123',
    order_id: 'order_12345',
    description: 'Premium subscription',
    metadata: { user_id: 'usr_123', plan: 'premium' },
    items: [
      {
        name: 'Premium Plan',
        quantity: 1,
        price: 10000
      }
    ]
  }.to_json

  response = http.request(request)
  result = JSON.parse(response.body)
  puts result['session_id']
  ```

  ```go Go theme={null}
  package main

  import (
      "bytes"
      "encoding/json"
      "fmt"
      "net/http"
  )

  func main() {
      url := "https://api.payviox.com/session"
      
      payload := map[string]interface{}{
          "amount":   10000,
          "currency": "USD",
          "customer": "customer_abc123",
          "order_id":    "order_12345",
          "description": "Premium subscription",
          "metadata": map[string]string{"user_id": "usr_123", "plan": "premium"},
          "items": []map[string]interface{}{
              {
                  "name":     "Premium Plan",
                  "quantity": 1,
                  "price":    10000,
              },
          },
      }
      
      jsonData, _ := json.Marshal(payload)
      
      req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
      req.Header.Set("Authorization", "Bearer pk_live_xxxxxxxxxxxx")
      req.Header.Set("Content-Type", "application/json")
      
      client := &http.Client{}
      resp, _ := client.Do(req)
      defer resp.Body.Close()
      
      var result map[string]interface{}
      json.NewDecoder(resp.Body).Decode(&result)
      fmt.Println(result["session_id"])
  }
  ```
</CodeGroup>

### Server-side Example (Secret API Key with IP)

When creating sessions from your backend, you can provide the client's IP address for fraud prevention:

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.payviox.com/session \
    -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
    -H "Content-Type: application/json" \
    -X POST \
    -d '{
      "amount": 10000,
      "currency": "USD",
      "customer": "customer_abc123",
      "order_id": "order_12345",
      "description": "Premium subscription",
      "ip": "203.0.113.42",
      "metadata": {"user_id": "usr_123", "plan": "premium"},
      "items": [
        {
          "name": "Premium Plan",
          "quantity": 1,
          "price": 10000
        }
      ]
    }'
  ```

  ```javascript Node.js theme={null}
  // Server-side example (Express.js)
  app.post('/create-payment', async (req, res) => {
    const clientIp = req.headers['x-forwarded-for'] || req.ip;
    
    const response = await fetch('https://api.payviox.com/session', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer sk_live_xxxxxxxxxxxx',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        amount: 10000,
        currency: 'USD',
        customer: req.body.customer,
        order_id: 'order_' + Date.now(),
        description: 'Premium subscription',
        ip: clientIp,
        metadata: { user_id: req.body.user_id, plan: 'premium' },
        items: req.body.items
      })
    });
    
    const data = await response.json();
    res.json({ session_id: data.session_id });
  });
  ```

  ```python Python (Flask) theme={null}
  from flask import Flask, request, jsonify
  import requests

  app = Flask(__name__)

  @app.route('/create-payment', methods=['POST'])
  def create_payment():
      client_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
      
      response = requests.post(
          'https://api.payviox.com/session',
          headers={
              'Authorization': 'Bearer sk_live_xxxxxxxxxxxx',
              'Content-Type': 'application/json'
          },
          json={
              'amount': 10000,
              'currency': 'USD',
              'customer': request.json['customer'],
              'order_id': f'order_{int(time.time())}',
              'description': 'Premium subscription',
              'ip': client_ip,
              'metadata': {'user_id': request.json['user_id'], 'plan': 'premium'},
              'items': request.json['items']
          }
      )
      
      return jsonify(response.json())
  ```

  ```php PHP (Laravel) theme={null}
  <?php

  Route::post('/create-payment', function (Request $request) {
      $clientIp = $request->header('X-Forwarded-For') ?? $request->ip();
      
      $response = Http::withHeaders([
          'Authorization' => 'Bearer sk_live_xxxxxxxxxxxx',
          'Content-Type' => 'application/json'
      ])->post('https://api.payviox.com/session', [
          'amount' => 10000,
          'currency' => 'USD',
          'customer' => $request->input('customer'),
          'order_id' => 'order_' . time(),
          'description' => 'Premium subscription',
          'ip' => $clientIp,
          'metadata' => ['user_id' => $request->input('user_id'), 'plan' => 'premium'],
          'items' => $request->input('items')
      ]);
      
      return response()->json($response->json());
  });
  ```
</CodeGroup>

<Tip>
  **Without IP parameter:** If you don't provide the `ip` parameter in server-side requests, no IP validation will be performed at payment time. This is useful when you can't reliably determine the client's IP.
</Tip>

## Direct Redirect (Skip Payment Page)

When you specify a `paymentMethodId` for a **redirect-based payment provider** (like Zen, Nicepay, PayPal, etc.), the API will automatically initiate the payment and return a `redirect_url` in the response.

This allows you to **bypass the Payviox payment page** entirely and redirect the customer directly to the payment provider.

<Info>
  This feature is only available for redirect-based providers. For other providers (like Stripe Elements), the standard flow applies.
</Info>

### How it works

1. Create a session with a `paymentMethodId` for a redirect-based provider
2. The API creates the session **and** initiates the payment
3. You receive both `session_id` and `redirect_url` in the response
4. Redirect your customer directly to the `redirect_url`

### Server-side Direct Redirect Example

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  app.post('/checkout', async (req, res) => {
    const clientIp = req.headers['x-forwarded-for'] || req.ip;
    
    const response = await fetch('https://api.payviox.com/session', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer sk_live_xxxxxxxxxxxx',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        amount: 10000,
        currency: 'USD',
        customer: req.body.customer,
        order_id: 'order_' + Date.now(),
        description: 'Premium subscription',
        paymentMethodId: 'zen_card', // Redirect-based provider
        ip: clientIp,
        items: req.body.items
      })
    });
    
    const data = await response.json();
    
    // If redirect_url is present, redirect directly to provider
    if (data.redirect_url) {
      return res.redirect(data.redirect_url);
    }
    
    // Otherwise, redirect to Payviox payment page
    res.redirect(`https://secure.payviox.com/${data.session_id}`);
  });
  ```

  ```php PHP (Laravel) theme={null}
  Route::post('/checkout', function (Request $request) {
      $clientIp = $request->header('X-Forwarded-For') ?? $request->ip();
      
      $response = Http::withHeaders([
          'Authorization' => 'Bearer sk_live_xxxxxxxxxxxx',
      ])->post('https://api.payviox.com/session', [
          'amount' => 10000,
          'currency' => 'USD',
          'customer' => $request->input('customer'),
          'order_id' => 'order_' . time(),
          'description' => 'Premium subscription',
          'paymentMethodId' => 'zen_card', // Redirect-based provider
          'ip' => $clientIp,
          'items' => $request->input('items')
      ]);
      
      $data = $response->json();
      
      // If redirect_url is present, redirect directly to provider
      if (isset($data['redirect_url'])) {
          return redirect()->away($data['redirect_url']);
      }
      
      // Otherwise, redirect to Payviox payment page
      return redirect()->away('https://secure.payviox.com/' . $data['session_id']);
  });
  ```

  ```python Python (Flask) theme={null}
  @app.route('/checkout', methods=['POST'])
  def checkout():
      client_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
      
      response = requests.post(
          'https://api.payviox.com/session',
          headers={
              'Authorization': 'Bearer sk_live_xxxxxxxxxxxx',
              'Content-Type': 'application/json'
          },
          json={
              'amount': 10000,
              'currency': 'USD',
              'customer': request.json['customer'],
              'order_id': f'order_{int(time.time())}',
              'description': 'Premium subscription',
              'paymentMethodId': 'zen_card',  # Redirect-based provider
              'ip': client_ip,
              'items': request.json['items']
          }
      )
      
      data = response.json()
      
      # If redirect_url is present, redirect directly to provider
      if 'redirect_url' in data:
          return redirect(data['redirect_url'])
      
      # Otherwise, redirect to Payviox payment page
      return redirect(f"https://secure.payviox.com/{data['session_id']}")
  ```
</CodeGroup>

### Redirect-based Providers

The following providers support direct redirect:

| Provider   | Payment Methods                       |
| ---------- | ------------------------------------- |
| Zen        | `zen_card`, `zen_blik`, etc.          |
| Nicepay    | `nicepay_va`, `nicepay_ewallet`, etc. |
| PayPal     | `paypal`                              |
| Payssion   | Various local payment methods         |
| Pallapay   | Crypto payments                       |
| Crypto.com | `cryptocom`                           |

<Tip>
  Use the [Get Payment Methods](/api/endpoints/get-payment-methods) endpoint to discover which payment methods are available for your account.
</Tip>

### Handling Payment Errors

If the payment initiation fails (e.g., provider API error), the response will include `payment_error` along with the `session_id`. You can then fall back to the standard flow:

```json theme={null}
{
  "session_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "payment_error": {
    "success": false,
    "message": "Payment creation failed",
    "error_type": "http_error"
  }
}
```

<Warning>
  When `payment_error` is present, the session is still created. You can redirect the customer to `https://secure.payviox.com/{session_id}` to let them try another payment method.
</Warning>

## Example Response

<ResponseExample>
  ```json Standard Response (201) theme={null}
  {
    "session_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
  }
  ```

  ```json Direct Redirect Response (201) theme={null}
  {
    "session_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
    "redirect_url": "https://secure.zen.com/checkout/abc123xyz"
  }
  ```

  ```json Payment Error Response (201) theme={null}
  {
    "session_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
    "payment_error": {
      "success": false,
      "message": "Payment creation failed",
      "error_type": "http_error"
    }
  }
  ```

  ```json Validation Error (400) theme={null}
  {
    "amount": [
      "The amount field is required."
    ],
    "currency": [
      "The currency field is required."
    ]
  }
  ```

  ```json Unauthorized (401) theme={null}
  {
    "error": "Unauthorized"
  }
  ```

  ```json Rate Limited (429) theme={null}
  {
    "error": "rate_limit_exceeded",
    "message": "Too many session requests",
    "type": "ip",
    "retry_after": 60
  }
  ```
</ResponseExample>

## Error Responses

<AccordionGroup>
  <Accordion title="400 — Validation Error">
    Returned when request body fields are missing or invalid. The response contains an object where each key is a field name and the value is an array of error messages.

    **Common causes:**

    * Required fields missing (`amount`, `currency`, `customer`, `order_id`, `items`)
    * Amount does not match the sum of item prices
    * Invalid item structure (missing name, quantity, or price)

    ```json theme={null}
    {
      "amount": ["The amount field is required."],
      "currency": ["The currency field is required."]
    }
    ```
  </Accordion>

  <Accordion title="400 — Payment Method Not Found">
    Returned when the `paymentMethodId` parameter references a payment method that does not exist or is not enabled for your business.

    **How to resolve:** Use the [Get Payment Methods](/api/endpoints/get-payment-methods) endpoint to list available methods.

    ```json theme={null}
    {
      "error": "Payment method not found"
    }
    ```
  </Accordion>

  <Accordion title="401 — Unauthorized">
    Returned when the API key is missing or invalid.

    **How to resolve:** Ensure you are sending a valid API key in the `Authorization: Bearer` header.

    ```json theme={null}
    {
      "error": "Unauthorized"
    }
    ```
  </Accordion>

  <Accordion title="429 — Rate Limit Exceeded">
    Returned when too many requests are sent in a short period of time, either per customer or per IP address.

    The response includes a `retry_after` field (in seconds) and a `Retry-After` HTTP header indicating when you can retry.

    **How to resolve:** Wait for the duration indicated by `retry_after` before retrying. Implement exponential backoff in your integration.

    ```json theme={null}
    {
      "error": "rate_limit_exceeded",
      "message": "Too many session requests",
      "type": "ip",
      "retry_after": 60
    }
    ```
  </Accordion>
</AccordionGroup>

<Note>
  The **IP mismatch** error (403) occurs at **payment time**, not during session creation. If you provided an `ip` parameter when creating the session, the payment page will verify that the user's IP matches. This is a fraud prevention measure.
</Note>

## Next Steps

After creating a session:

<CardGroup cols={2}>
  <Card title="Redirect Customer" icon="arrow-up-right-from-square" href="/sdk/integration/redirect">
    Redirect the customer to the payment page using the session ID
  </Card>

  <Card title="Get Payment Methods" icon="credit-card" href="/api/endpoints/get-payment-methods">
    Fetch available payment methods for the session
  </Card>

  <Card title="Dashboard" icon="chart-line" href="https://dash.payviox.com">
    Monitor your payments in real-time
  </Card>

  <Card title="API Playground" icon="flask" href="/api/authentication#using-the-api-playground">
    Test API calls directly from your browser
  </Card>
</CardGroup>

## Validation Rules

<AccordionGroup>
  <Accordion title="Amount Validation">
    * Minimum amount: 100 cents (\$1.00)
    * Must be a positive integer
    * Total amount must equal the sum of all items (price × quantity)
  </Accordion>

  <Accordion title="Items Validation">
    * At least one item is required
    * Each item must have a name, quantity, and price
    * Quantity must be at least 1
    * Price must be at least 1 cent
  </Accordion>

  <Accordion title="Order ID">
    * Must be unique per business
    * Used for tracking and webhooks
    * Recommended to use your internal order/transaction ID
  </Accordion>
</AccordionGroup>


## OpenAPI

````yaml POST /session
openapi: 3.0.3
info:
  title: Payviox API
  description: Payment processing API
  version: 1.0.0
servers:
  - url: https://api.payviox.com
security:
  - bearerAuth: []
paths:
  /session:
    post:
      tags:
        - Sessions
      summary: Create Session
      description: Create a new payment session
      operationId: createSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSessionRequest'
      responses:
        '201':
          description: Session created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateSessionResponse'
        '400':
          description: Bad request
        '401':
          description: Unauthorized
components:
  schemas:
    CreateSessionRequest:
      type: object
      required:
        - amount
        - currency
        - customer
        - order_id
        - items
      properties:
        amount:
          type: integer
          minimum: 100
          description: Payment amount in cents (e.g., 10000 = $100.00)
        currency:
          type: string
          maxLength: 3
          description: Three-letter ISO currency code (e.g., USD)
        customer:
          type: string
          description: Customer identifier (email, customer ID, or unique identifier)
        order_id:
          type: string
          description: Your unique order identifier
        items:
          type: array
          description: Array of items being purchased
          items:
            type: object
            additionalProperties: true
          example:
            - name: Premium Plan
              quantity: 1
              price: 10000
        description:
          type: string
          description: Optional description of the payment
        paymentMethodId:
          type: string
          description: Force a specific payment method (optional)
        ip:
          type: string
          description: Server-side only. The end-user's IP address
        metadata:
          type: object
          description: >-
            Custom key-value pairs to attach to this session. These will be
            returned in webhook notifications.
          additionalProperties: true
          example:
            user_id: usr_123
            campaign: summer_sale
    CreateSessionResponse:
      type: object
      properties:
        session_id:
          type: string
          description: Unique identifier for the created session
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````