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

# Verify from checkout

> A pay-screen helper that confirms the outcome of the payment for this access code. It returns amount, status, channel and timestamps, and it stays scoped to the session.

Use it to decide what the customer sees. Server-side fulfilment must use `GET /payment/verify/{reference}` with your secret key.




## OpenAPI

````yaml /openapi.yaml get /payment/checkout/verify
openapi: 3.1.0
info:
  title: Vestrapay Payments API
  version: '1.0'
  summary: >-
    Initialise payments, collect them through hosted or custom checkout, and
    verify the result from your server.
  description: >
    The Vestrapay Payments API lets your server create a payment, lets your
    customer pay it, and lets your server confirm the outcome before it fulfils
    an order.


    ## How a payment flows


    1. **Initialise** (`POST /payment/initialize`, secret key). Your server
    creates a pending transaction and receives a `reference`, an `accessCode`
    and a hosted `checkoutUrl`.

    2. **Collect.** Send the customer to the `checkoutUrl`, or build your own
    pay screen with the **Checkout** endpoints, which use the `accessCode`
    rather than your secret key. Four channels are available: `card`,
    `bank_transfer`, `bank_payment` and `ussd`.

    3. **Confirm** (`GET /payment/verify/{reference}`, secret key). Fulfil only
    when `data.status` is `success` and `data.amount` matches the order.
    Webhooks and the browser callback are signals. Verify is the confirmation.


    ## Environments and servers


    Sandbox and live are separate environments with separate keys, data,
    services and webhook endpoints. A sandbox reference cannot be verified on
    live, and the reverse.


    | Environment | API base URL | Checkout host | Merchant dashboard |

    | --- | --- | --- | --- |

    | Sandbox | `https://api.sandbox.vestrapay.app/v1` |
    `https://checkout.sandbox.vestrapay.app` |
    `https://merchant.sandbox.vestrapay.app` |

    | Live | `https://api.vestrapay.com/v1` | `https://checkout.vestrapay.com` |
    `https://merchant.vestrapay.com` |


    Keys begin `sk_test_` for the sandbox and `sk_live_` for live. A key sent to
    the wrong host is rejected with `invalid_key_environment`. Live payments are
    refused until your business has passed KYB.


    ## Authentication


    | Scheme | Header | Used for |

    | --- | --- | --- |

    | Secret key | `x-api-key: sk_…` or `Authorization: Bearer sk_…` |
    Initialise and verify. Server-side only. Never ship it to a browser or
    mobile app. |

    | Access code | `x-access-code: …` (or the `access_code` query parameter) |
    Every Checkout endpoint. Safe to hand to the customer's browser, because it
    is scoped to one payment and expires after 60 minutes. |


    ## Amounts


    Amounts are **decimal strings in major units**. `"100.00"` is one hundred
    naira. Never send kobo, and never send a formatted value such as `"₦1,000"`.
    Compare amounts as strings or decimals, never as binary floats. NGN is the
    supported currency.


    ## Response envelope


    Every JSON response uses one of two envelopes.


    ```json

    { "status": "success", "data": { } }

    ```


    ```json

    { "status": "failed", "error": "invalid_credentials", "message": "The
    provided credentials are invalid." }

    ```


    Branch on `error`, which is stable. The `message` is for people and may
    change wording. The only unwrapped responses are the checkout event stream
    (`text/event-stream`) and webhook bodies, which are `{ "event", "data",
    "timestamp" }`. Log the `X-Request-Id` response header with every failure
    and quote it to support.


    ## Splitting a payment


    Services, sub-accounts and splits are configured in the merchant dashboard,
    and each service has a **service key**. To divide a particular payment, pass
    `distribution` on initialise as a map of service key to an absolute amount
    in the same unit as `amount`. Whatever you do not allocate settles to your
    primary settlement account. Verify returns a `split` summary once the
    payment has been allocated.


    ## Statuses at a glance


    | Where | Values |

    | --- | --- |

    | Payment (initialise, verify, status) | `pending`, `processing`, `success`,
    `failed`, `abandoned` |

    | Card charge (`data.status`) | `fingerprint_required`, `3ds_required`,
    `processing`, `success`, `failed` |

    | Transfer / USSD charge (`data.status`) | `awaiting_transfer`,
    `awaiting_ussd`, plus the processing states above |

    | Bank payment charge (`data.status`) | `processing` (with `redirectUrl`
    when the bank supplies one). The event stream may emit
    `checkout.awaiting_bank_authorization`. |


    Only `success` on verify means paid.


    ## Errors and retries


    | HTTP status | Meaning | Retry? |

    | --- | --- | --- |

    | 400 | Invalid request, or the current state does not allow it. | No. Fix
    it first. |

    | 401 | Credentials are missing, malformed, expired or wrong. | No. |

    | 403 | Authenticated but not allowed, for example KYB not approved or live
    not enabled. | No. |

    | 404 | The thing does not exist in this environment or for your account. |
    No. |

    | 409 | Conflict with existing state. | No. |

    | 410 | The session or access code has expired. | No. Initialise again. |

    | 422 | Too many attempts on this payment. | No. Initialise again. |

    | 502, 503 | A bank or processor dependency is unavailable. | Yes, with
    exponential backoff. |


    Verify is read-only, so retry it on network failure or a 5xx response.
    Initialise creates a new reference on every call, so a retried initialise
    can create a second pending payment. Carry your own order ID in `metadata`
    so that you can recognise and ignore duplicates.


    ## Not in this reference


    Managing services, sub-accounts, splits, partner users, settlements, payment
    links, webhooks and KYB is done in the merchant dashboard and is covered in
    the guides. This reference documents the Payments API only.
  contact:
    name: Vestrapay developer support
    email: tech@vestrapay.com
    url: https://vestrapay.com
servers:
  - url: https://api.sandbox.vestrapay.app/v1
    description: Sandbox. Use `sk_test_` keys. No real money moves.
  - url: https://api.vestrapay.com/v1
    description: >-
      Live. Use `sk_live_` keys. Available once KYB is approved and live mode is
      enabled.
security:
  - SecretKey: []
  - BearerAuth: []
tags:
  - name: Payments
    description: >
      Server-to-server calls made with your **secret key**.


      **Initialise** creates a pending transaction and the session your customer
      pays through. **Verify** is the canonical lookup of a payment's state and
      the only call you should trust before you fulfil an order. Both calls
      belong on your server.
  - name: Checkout
    description: >
      Customer-session calls made with the **access code** returned by
      initialise. Hosted checkout uses these for you. Call them yourself only
      when you build a custom pay screen.


      The access code is valid for 60 minutes and is scoped to a single payment.
      Choose a channel, charge it, then confirm from your server with verify.


      | Channel | Calls |

      | --- | --- |

      | Card | Create a card session, mount the processor's hosted fields,
      charge, and authenticate if 3-D Secure asks for it. |

      | Transfer | Request a one-time virtual account and show it to the
      customer. In the sandbox, simulate the outcome. |

      | Pay with bank | List banks, then start the charge for the bank the
      customer picks. |

      | USSD | Start the charge with the customer's phone number and show the
      dial string. |


      The status, verify and events calls let a pay screen show progress. They
      do not replace server-side verify.
paths:
  /payment/checkout/verify:
    get:
      tags:
        - Checkout
      summary: Verify from checkout
      description: >
        A pay-screen helper that confirms the outcome of the payment for this
        access code. It returns amount, status, channel and timestamps, and it
        stays scoped to the session.


        Use it to decide what the customer sees. Server-side fulfilment must use
        `GET /payment/verify/{reference}` with your secret key.
      operationId: checkout-verify
      responses:
        '200':
          description: Checkout-side verification of the payment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CheckoutVerifyResponse'
              example:
                status: success
                data:
                  reference: VPY_TXN_ACME001
                  amount: '500.00'
                  total: '501.00'
                  currency: NGN
                  status: success
                  channel: bank_transfer
                  fees: '1.00'
                  paidAt: '2026-05-10T14:30:00.000Z'
                  failedAt: null
        '401':
          $ref: '#/components/responses/CheckoutUnauthorized'
        '404':
          description: No payment exists for this access code.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                status: failed
                error: transaction.not_found
                message: Transaction not found.
      security:
        - AccessCode: []
        - AccessCodeQuery: []
components:
  schemas:
    CheckoutVerifyResponse:
      type: object
      required:
        - status
        - data
      properties:
        status:
          type: string
          enum:
            - success
        data:
          type: object
          properties:
            reference:
              type: string
              description: The payment reference.
            amount:
              type: string
              description: The amount you initialised, in major units.
            total:
              type: string
              description: >-
                What the customer paid, including the fee when the customer
                bears it.
            currency:
              type: string
              example: NGN
            status:
              type: string
              enum:
                - pending
                - processing
                - success
                - failed
                - abandoned
              description: The current payment state.
            channel:
              type:
                - string
                - 'null'
              description: The channel the customer used, or `null` before they choose one.
            fees:
              type: string
              description: The fee on this transaction, in major units.
            paidAt:
              type:
                - string
                - 'null'
              format: date-time
              description: When the payment succeeded, or `null`.
            failedAt:
              type:
                - string
                - 'null'
              format: date-time
              description: When the payment failed, or `null`.
    ErrorEnvelope:
      type: object
      description: >-
        The envelope for every failed request. Branch on `error`, which is
        stable. Show `message` to people but never parse it.
      required:
        - status
        - error
        - message
      properties:
        status:
          type: string
          enum:
            - failed
        error:
          type: string
          description: >-
            A stable machine-readable error code, such as `invalid_credentials`
            or `payment.invalid_amount`.
          example: invalid_credentials
        message:
          type: string
          description: A human-readable explanation. Wording may change.
          example: The provided credentials are invalid.
  responses:
    CheckoutUnauthorized:
      description: >
        The access code is missing, invalid or expired. Common codes are
        `missing_access_code` and `transaction_expired`. Initialise a new
        payment if the code has expired.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            status: failed
            error: missing_access_code
            message: The x-access-code header is required.
  securitySchemes:
    SecretKey:
      type: apiKey
      in: header
      name: x-api-key
      description: >
        Your secret key. `sk_test_…` on the sandbox, `sk_live_…` on live.
        Server-side only.

        Copy it from **Settings → Developers** in the merchant dashboard.

        `Authorization: Bearer` with the same value is also accepted.
    BearerAuth:
      type: http
      scheme: bearer
      description: >-
        The same secret key sent as `Authorization: Bearer sk_…`. An alternative
        to `x-api-key`.
    AccessCode:
      type: apiKey
      in: header
      name: x-access-code
      description: >
        The `accessCode` returned by initialise. Scoped to one payment and valid
        for 60 minutes.

        Safe to give to the customer's browser.
    AccessCodeQuery:
      type: apiKey
      in: query
      name: access_code
      description: >-
        The same access code sent as the `access_code` query parameter. An
        alternative to `x-access-code`.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.