payout / developers
Guide

Simple payment

On this page

Accept a one-off payment: create a checkout, send the customer to the Payout payment page and mark the order as paid when the webhook confirms it.

Steps

  1. Get an API key

    Generate an API key (client ID and secret) in the backoffice: Sandbox or Production. You need a Payout account first.

    Set a notify URL before you generate the key. Payout sends webhooks (POST requests) about checkouts and other events to this URL.

  2. Get a Bearer token

    Exchange the client ID and secret for a token:

    Command Line
    curl --location --request POST 'https://sandbox.payout.one/api/v1/authorize' \
    --header 'Content-Type: application/json' \
    --header 'Accept: application/json' \
    --data-raw '{
        "client_id": "DC995618-7ED8-4070-9DA0-48B6F86551C3",
        "client_secret": "q3dpHpYtDrH-KmGD4HMn5OTEx6IsZPBokQ8CqMONWqMSEePWy9bXd3Ua3KvO7f6C"
    }'
    

    Response:

    JSON
    {
        "token": "SFMyNTY.EXAMPLE-TOKEN.dGhpcy1pcy1hLWZha2Utc2lnbmF0dXJlLWV4YW1wbGU",
        "valid_for": 6000
    }
    

    Send the token in the Authorization: Bearer header of every other request. It is valid for valid_for seconds; then request a new one.

  3. Create the checkout

    Send the details of your order. Give every checkout its own Idempotency-Key: a request with a key that was already used returns the existing checkout instead of creating a new one.

    Command Line
    curl --location --request POST 'https://sandbox.payout.one/api/v1/checkouts' \
    --header 'Content-Type: application/json' \
    --header 'Accept: application/json' \
    --header 'Authorization: Bearer SFMyNTY.EXAMPLE-TOKEN.dGhpcy1pcy1hLWZha2Utc2lnbmF0dXJlLWV4YW1wbGU' \
    --header 'Idempotency-Key: d2c9f095-8e4c-498a-802a-68c220dbe1bd' \
    --data-raw '{
        "amount": 300,
        "currency": "EUR",
        "customer": {
            "first_name": "John",
            "last_name": "Doe",
            "email": "[email protected]",
            "phone": "+421900000000"
        },
        "billing_address": {
            "name": "John Doe",
            "address_line_1": "Billing Address Line 1",
            "address_line_2": "Billing Address Line 2",
            "city": "Billington",
            "postal_code": "BL92883",
            "country_code": "GB"
        },
        "shipping_address": {
            "name": "John Doe",
            "address_line_1": "Shipping Address Line 1",
            "address_line_2": "Shipping Address Line 2",
            "city": "Shippington",
            "postal_code": "W153KF",
            "country_code": "GB"
        },
        "products": [
            {
                "name": "Product 1",
                "unit_price": 100,
                "quantity": 3
            }
        ],
        "external_id": "order-1001",
        "nonce": "bzAwd2VzTU5UMEtWZ3IzMA",
        "metadata": {
            "source": "eshop"
        },
        "redirect_url": "https://eshop.example.com/payment/redirect",
        "signature": "7aff78519a0d038ba4960a53a1bc82f0ff35ce7b84d9cbe367360295652f07fb"
    }'
    

    amount is in cents: 300 is 3.00 EUR. The same applies to other currencies, so 30000 is 300 CZK. Only amount, currency, customer, external_id, nonce, redirect_url and signature are required; Create checkout lists all fields.

    To sign the request, join the values with |, hash the string with SHA-256 and send the hash as signature:

    TEXT
    Pattern: amount|currency|external_id|nonce|client_secret
    Input:   300|EUR|order-1001|bzAwd2VzTU5UMEtWZ3IzMA|q3dpHpYtDrH-KmGD4HMn5OTEx6IsZPBokQ8CqMONWqMSEePWy9bXd3Ua3KvO7f6C
    SHA-256: 7aff78519a0d038ba4960a53a1bc82f0ff35ce7b84d9cbe367360295652f07fb
    

    Note

    Every signature in the API is the SHA-256 hash in lowercase hex. Some libraries return uppercase hex; convert it to lowercase before you send or compare it.

  4. Verify the response

    The response contains the new checkout with its own nonce and signature. Compute the signature from the response values and compare it with signature to make sure the response comes from Payout.

    Response:

    JSON
    {
        "object": "checkout",
        "id": 141447,
        "external_id": "order-1001",
        "amount": 300,
        "currency": "EUR",
        "redirect_url": "https://eshop.example.com/payment/redirect",
        "idempotency_key": "d2c9f095-8e4c-498a-802a-68c220dbe1bd",
        "customer": {
            "first_name": "John",
            "last_name": "Doe",
            "name": "John Doe",
            "email": "[email protected]",
            "phone": "+421900000000",
            "note": null
        },
        "checkout_url": "https://sandbox.payout.one/checkouts/U0ZNeU5UWS5FWEFNUExFLUNIRUNLT1VULVRPS0VOLm5vdC1hLXJlYWwtc2lnbmF0dXJl/?account_id=RVhBTVBMRS1BQ0NPVU5ULVRPS0VOLTAwMDAwMDAwMDA",
        "metadata": {
            "source": "eshop"
        },
        "status": "processing",
        "nonce": "V2cwZ0U0YU9tNlVoOUlrbA",
        "signature": "0cce8d83001e51f45278a01a731566cf4805a9470d35cf1d58a1f1bf4fd98c0a",
        "payment": null,
        "all_payments": [],
        "billing_address": {
            "name": "John Doe",
            "address_line_1": "Billing Address Line 1",
            "address_line_2": "Billing Address Line 2",
            "city": "Billington",
            "postal_code": "BL92883",
            "country_code": "GB"
        },
        "shipping_address": {
            "name": "John Doe",
            "address_line_1": "Shipping Address Line 1",
            "address_line_2": "Shipping Address Line 2",
            "city": "Shippington",
            "postal_code": "W153KF",
            "country_code": "GB"
        },
        "products": [
            {
                "name": "Product 1",
                "quantity": 3,
                "unit_price": 100
            }
        ],
        "payment_token": "U0ZNeU5UWS5FWEFNUExFLUNIRUNLT1VULVRPS0VOLm5vdC1hLXJlYWwtc2lnbmF0dXJl",
        "is_status_final": false
    }
    

    Signature of this response:

    TEXT
    Pattern: amount|currency|external_id|nonce|client_secret
    Input:   300|EUR|order-1001|V2cwZ0U0YU9tNlVoOUlrbA|q3dpHpYtDrH-KmGD4HMn5OTEx6IsZPBokQ8CqMONWqMSEePWy9bXd3Ua3KvO7f6C
    SHA-256: 0cce8d83001e51f45278a01a731566cf4805a9470d35cf1d58a1f1bf4fd98c0a
    
  5. Redirect the customer to the payment page

    Send the customer to checkout_url from the response.

  6. Handle the customer's return

    When the payment succeeds or fails, Payout sends the customer back to the redirect_url of the checkout. The redirect doesn't carry the result of the payment; the webhook does.

  7. Receive the webhook

    Payout sends the result as a POST request with a JSON body to your notify URL. Respond with a 2xx status code; otherwise Payout sends the webhook again.

    checkout.succeeded payload:

    JSON
    {
        "external_id": "order-1001",
        "object": "webhook",
        "type": "checkout.succeeded",
        "data": {
            "object": "checkout",
            "id": 141447,
            "external_id": "order-1001",
            "amount": 300,
            "currency": "EUR",
            "redirect_url": "https://eshop.example.com/payment/redirect",
            "customer": {
                "first_name": "John",
                "last_name": "Doe",
                "email": "[email protected]",
                "card_number_masked": ""
            },
            "payment": {
                "object": "payment",
                "status": "successful",
                "payment_method": "card",
                "failure_reason": "",
                "created_at": 1759744800,
                "fee": 8,
                "net": 292
            },
            "metadata": {
                "source": "eshop"
            },
            "status": "succeeded",
            "is_status_final": true
        },
        "nonce": "eE1FNXBhS3ZxVklRZG5JZA",
        "signature": "3f2c8df4770fd18311f3360d96aacace00842b464a0317a45768a622531dc79d"
    }
    

    Note

    is_status_final tells whether the checkout has reached a final state and will not change any more. For succeeded checkouts it is always true. For expired checkouts it becomes true only 6 days after the expiry, so that late bank transfers can still arrive and be matched.

  8. Verify the webhook signature

    Compute the signature from the top-level external_id, type and nonce of the webhook and compare it with signature. Process the webhook only if they match.

    TEXT
    Pattern: external_id|type|nonce|client_secret
    Input:   order-1001|checkout.succeeded|eE1FNXBhS3ZxVklRZG5JZA|q3dpHpYtDrH-KmGD4HMn5OTEx6IsZPBokQ8CqMONWqMSEePWy9bXd3Ua3KvO7f6C
    SHA-256: 3f2c8df4770fd18311f3360d96aacace00842b464a0317a45768a622531dc79d
    
  9. Mark the order as paid

    Mark the order as paid once you have received and verified checkout.succeeded. Its is_status_final: true confirms that the checkout will not change any more.

    You can also listen for the checkout.finalized webhook, which is sent when is_status_final changes to true. It is useful for expired checkouts, which become final 6 days after they expire. checkout.finalized is available on request; contact support to enable it for your account.

Test cards

Use these cards in the sandbox.

Card number Expiry CVV 3-D Secure Result
4245757666349685 12/29 123 Challenge required Authorized
5150030090350186 12/29 123 3DS Method required, then frictionless success Authorized
4012001037141120 12/29 123 3DS Method and challenge required Authorized
5100052384536834 12/29 123 Challenge parameters if an SDK object is sent in OrderCreateRequest Authorized
5100052384536818 02/32 123 Challenge required; without 3-D Secure, soft decline (SSD) Authorized
5100052384536826 12/29 123 Frictionless, positive authentication Authorized
5521455186577727 12/29 123 Frictionless, negative authentication Not authorized (authentication fails)

Next steps

Was this page helpful?