Skip to content

Booking API quickstart

Last verified
Last verified Sep 23, 2026

This tutorial sells a voucher through the Booking API and hands payment to the shop's hosted payment page. It uses six requests: find the voucher, add it to a cart, add the customer, read the required legal documents, complete checkout and check the payment. The same steps sell tickets; Show dates and time slots with the Booking API shows how to add dated tickets to the cart instead.

Before you start

  • A shop in KORONA Event that sells at least one published voucher.
  • The shop's domain, for example tickets.example.com, and your KORONA Event API host.
  • A tool that sends HTTP POST requests with a JSON body, such as curl or your application's HTTP client.

Send every request as POST https://<api-host>/api/graphql/booking/v1 with these headers:

http
Content-Type: application/json
X-Tenant-Domain: tickets.example.com
Accept-Language: en

The body is a JSON object with query and variables. How the Booking API works explains the records and tokens used below.

1. Find the voucher

List the shop's vouchers. posId: "auto" selects the shop's own sales channel.

graphql
query DiscoverVouchers {
  offers(posId: "auto", offerableTypes: [VOUCHER_CONFIGURATION], first: 20) {
    edges {
      node {
        ... on VoucherConfiguration {
          id
          nameTranslated
          currentPriceValue
        }
      }
    }
  }
}
json
{
  "data": {
    "offers": {
      "edges": [
        {
          "node": {
            "id": "2a1dc9d4-fb0b-4dd5-808f-858033098f31",
            "nameTranslated": "Gift voucher",
            "currentPriceValue": { "amount": 5000, "currency": "EUR" }
          }
        }
      ]
    }
  }
}

Money values contain the amount in the currency's smallest unit, so 5000 is 50.00 EUR.

2. Add the voucher to a cart

The first cart mutation without a requestId creates the cart. Keep the returned accessToken: it is the cart token for every later request.

graphql
mutation AddToCart($input: RequestItemCreateInput!) {
  requestItemCreate(input: $input) {
    request {
      accessToken
      totalGrossValue
      checkoutHold {
        expiresAt
        secondsRemaining
      }
    }
    requestItem {
      id
    }
    errors {
      key
      message
      messageTranslated
    }
  }
}
json
{
  "input": {
    "offerableType": "VOUCHER_CONFIGURATION",
    "offerableId": "2a1dc9d4-fb0b-4dd5-808f-858033098f31",
    "pricings": [
      {
        "quantity": 1,
        "priceOriginType": "VOUCHER_CONFIGURATION",
        "priceOriginId": "2a1dc9d4-fb0b-4dd5-808f-858033098f31"
      }
    ]
  }
}

The response contains the cart token, the cart total and the checkout hold. To add another item to the same cart, send the cart token as requestId in the next requestItemCreate.

3. Add the customer

Send the customer's details with the cart token as id. Add a billing address; mark it with billing: true.

graphql
mutation AddCustomer($input: RequestUpdateInput!) {
  requestUpdate(input: $input) {
    request {
      contact {
        email
      }
      billingAddress {
        city
      }
      checkoutPolicy {
        compatible
        targetState
      }
      requiresShipping
      customFieldsCompletion {
        allRequiredComplete
      }
    }
    errors {
      key
      message
      messageTranslated
    }
  }
}
json
{
  "input": {
    "id": "<cart token>",
    "contact": {
      "firstName": "Alex",
      "lastName": "Example",
      "email": "alex@example.com",
      "customer": {
        "addresses": [
          {
            "billing": true,
            "street": "Example Street 1",
            "postalCode": "12345",
            "city": "Berlin",
            "country": "DE"
          }
        ]
      }
    }
  }
}

Check the returned cart before checkout:

FieldWhat to do
checkoutPolicy.targetState is BOOKEDContinue with this tutorial.
checkoutPolicy.compatible is falseThe cart mixes offers that cannot be checked out together. Remove the lines listed in checkoutPolicy.conflicts.
requiresShipping is trueAdd an address with shipping: true in a country from shop.allowedShippingCountries.
customFieldsCompletion.allRequiredComplete is falseDo not start checkout. The Booking API cannot collect these answers; send the customer to the hosted shop to complete this purchase.

Customers must accept the shop's required legal documents. Show each document's nameTranslated and checkboxLabelTranslated with a checkbox, and link to the document text from legalDocument(id:).

graphql
query RequiredDocuments {
  legalDocuments(required: [true], first: 20) {
    edges {
      node {
        id
        nameTranslated
        checkboxLabelTranslated
        publishedAt
      }
    }
  }
}

Keep each document's id and publishedAt for the next step. publishedAt identifies the version the customer accepted.

5. Complete checkout

After the customer accepts the documents, complete checkout. The response contains the invoice and the hosted payment link.

graphql
mutation CompleteCheckout($input: RequestPaymentInitiateInput!) {
  requestPaymentInitiate(input: $input) {
    request {
      state
    }
    invoice {
      accessToken
      paymentState
      paymentLink
    }
    errors {
      key
      message
      messageTranslated
    }
  }
}
json
{
  "input": {
    "id": "<cart token>",
    "legalDocumentAcceptances": [
      { "legalDocumentId": "1", "legalDocumentVersion": "2026-09-23T19:21:22+00:00" },
      { "legalDocumentId": "2", "legalDocumentVersion": "2026-09-23T19:21:22+00:00" }
    ]
  }
}
json
{
  "data": {
    "requestPaymentInitiate": {
      "request": { "state": "BOOKED" },
      "invoice": {
        "accessToken": "<invoice token>",
        "paymentState": "REQUIRES_PAYMENT",
        "paymentLink": "https://tickets.example.com/en/payments/<token>"
      },
      "errors": null
    }
  }
}

Redirect the customer's browser to paymentLink. The customer chooses a payment method and pays on the hosted payment page, which uses the language of your Accept-Language header.

6. Check the payment

Read the invoice with the invoice token to follow the payment. paymentState becomes PAID when the payment succeeds.

graphql
query PaymentStatus($invoiceToken: IdOrNumberOrToken!) {
  invoice(id: $invoiceToken) {
    paymentState
    paymentIntentCreateError
    payments {
      state
      providerInitializationFailed
    }
    request {
      state
      checkoutHold {
        secondsRemaining
      }
      ticketsUrl: shareablePublicTicketsUrl(format: PDF)
    }
  }
}

The tickets URL can be present while ticket activation is pending. It may open the hosted ticket page, which makes the tickets available when activation finishes. Do not use the URL's presence as a signal that a PDF is ready.

Check your work

  • You stored the cart token from step 2 and used it in steps 3 and 5.
  • The customer accepted every required legal document before step 5.
  • The customer landed on the hosted payment page from paymentLink.