Create bookings collected by an external partner

Availability
Beta
Last verified
Last verified Nov 6, 2026

Use this server-to-server flow when an external booking system collects payment from the guest and settles with the venue later. KORONA Event creates the booking and a fiscal delivery note for the configured reseller. The venue continues its agreed settlement process in KORONA.studio.

Before you start

Obtain the API base URL, tenant domain, service-account API key, point-of-sale ID and existing order-channel ID from the venue's integration administrator. Use an existing test tenant where available. Keep API keys on your server and out of browser bundles, logs and examples.

The administrator must enable fiscal delivery notes and configure the order channel's integration row for your service account, including the reseller contact/customer and its KORONA.studio customer mapping. The guest contact supplied below is separate from that billing customer. The mapping is stored when the order is created; later configuration changes do not move existing orders to another reseller.

The current service-account role needs order create/read/update permissions, plus the read permissions for the offer, price, order-item and contact fields used in the recipe. Cancellation also uses order update permission. These are ordinary role permissions, not an integration-ownership scope. See Create a service account.

Send each operation as a separate HTTPS POST to /api/graphql/booking/v1:

http
Content-Type: application/json
Authorization: Bearer <service-account API key>
X-Tenant-Domain: <tenant domain>
Accept-Language: en

Use a JSON body containing query and variables. Keep the API key on every call, including calls using the returned cart token. A cart token alone does not grant partner payment authority. Management API credentials are not accepted by this endpoint.

Use the same configured posId for discovery, prices and creation. An explicit POS takes precedence over automatic defaults. With automatic creation, the channel/user POS override precedes the user's default POS, then the channel default. The explicit POS in this recipe avoids a different context between reading prices and creating the order. The order channel and POS are configuration, not permission grants.

Discover offers and visit times

graphql
query PartnerOffers($posId: ID!, $after: String) {
  offers(posId: $posId, offerableTypes: [EVENT_TEMPLATE], first: 50, after: $after) {
    edges {
      node {
        ... on EventTemplate {
          id
          nameTranslated
          timeZone
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

For the selected event template, request a bounded date range and the intended quantity:

graphql
query PartnerSlots($id: ID!, $posId: ID!, $span: TsRange!, $quantity: Int!, $after: String) {
  offer(type: EVENT_TEMPLATE, id: $id, posId: $posId) {
    ... on EventTemplate {
      potentialEvents(posId: $posId, span: $span, spanOperator: CONTAINS_START, quantity: $quantity, first: 50, after: $after) {
        edges {
          node {
            span
            remainingQuota
            priceRules(posId: $posId) {
              id
              minPax
              maxPax
              priceCategory {
                id
                nameTranslated
              }
              product {
                currentPriceValue
              }
            }
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
  }
}

Pass a half-open range, for example [2026-12-01T00:00:00+01:00,2026-12-02T00:00:00+01:00). Use each returned slot's span unchanged when creating its item. Display times in the offer's IANA timeZone; do not add a fixed timezone offset yourself. Continue pagination with endCursor while hasNextPage is true.

Read prices from the selected occurrence's price rules. Money is a JSON scalar such as {"amount": 2500, "currency": "EUR"}: the amount is in minor currency units (€25.00 here). Prices and capacity can change; discovery does not reserve places or fix the final price. Campaign prices or dedicated contingents must be configured and agreed separately, not inferred from this API flow. See Dates and times.

Create the order

graphql
mutation PartnerCreate($input: RequestCreateInput!) {
  requestCreate(input: $input) {
    request {
      id
      accessToken
      number
      externalRef
      paymentMethod
      state
      totalGrossValue
      requestItems {
        id
        offerableId
        offerableType
        offerableSpan
        pricings {
          id
          quantity
          priceValue
        }
      }
    }
    errors {
      key
      message
      messageTranslated
    }
  }
}
json
{
  "input": {
    "posId": "<configured POS ID>",
    "requestChannel": { "id": "<configured order-channel ID>" },
    "paymentMethod": "DELIVERY_NOTE",
    "externalRef": "PARTNER-ORDER-123",
    "contact": {
      "firstName": "Alex",
      "lastName": "Example",
      "email": "[email protected]",
      "customer": {}
    },
    "requestItems": [
      {
        "offerableType": "EVENT_TEMPLATE",
        "offerableId": "<selected template ID>",
        "offerableSpan": "<returned slot span>",
        "pricings": [{ "priceOriginType": "PRICE_RULE", "priceOriginId": "<returned price-rule ID>", "quantity": 1 }]
      }
    ]
  }
}

Set externalRef to the external system's order number. It is the reference staff use to match the two systems and is included as the fiscal delivery-note comment. It is separate from KORONA Event's number, externalId and an idempotency key. It is not unique and does not prevent duplicate creation.

Keep the returned id, number and accessToken securely. Inspect the returned prices and total before confirmation. Creating a timed template item can return an EVENT item with a new event ID; use the returned item identity for subsequent changes.

Supply required attendee/personalization data and complete any required forms before confirming. This minimal example assumes an offer without additional required questions. See Collect booking and attendee details for the supported collection flow.

Confirm without charging the guest again

graphql
mutation PartnerConfirm($input: RequestFinalizeInput!) {
  requestFinalize(input: $input) {
    request {
      id
      number
      state
      externalRef
      paymentMethod
      paymentState
      shareablePublicTicketsUrl(format: PDF)
    }
    invoice {
      number
    }
    errors {
      key
      message
      messageTranslated
    }
  }
}
json
{ "input": { "id": "<returned cart accessToken>", "paymentMethod": "DELIVERY_NOTE" } }

A successful delivery-note confirmation returns state: BOOKED and no new invoice for guest payment. It creates the fiscal delivery note for the reseller mapping. Do not call paymentIntentCreate, open hosted payment or use the BOOKING_KIT payment provider to represent money your system collected.

paymentState can remain REQUIRES_PAYMENT: the guest's payment to the partner does not mean the venue has received the partner's settlement. Do not use that state alone to decide whether this delivery-note booking is confirmed or its tickets may be delivered.

Use the returned ticket URL unchanged when present; treat it as a credential. If it is absent, read the order again after ticket preparation. Do not create another order to obtain a ticket link. Ticket output depends on the venue's ticket configuration, and a link alone is not confirmation of the partner's payout.

Ticket activation and PDF preparation can finish asynchronously. If downloading returns HTTP 503 with Retry-After, wait for that interval and retry the same URL.

Read the result and recover an uncertain response

graphql
query PartnerStatus($id: IdOrNumberOrToken!) {
  request(id: $id) {
    id
    number
    state
    externalRef
    paymentMethod
    paymentState
    totalGrossValue
    shareablePublicTicketsUrl(format: PDF)
  }
}

Use the stored cart token as id, with the API-key header. After a confirmation timeout, read this result before attempting further writes. If it is already BOOKED, use that booking. If creation times out before you receive an identifier, reconcile with the venue using your external order number before creating again. This contract does not promise automatic idempotent retries or lookup by externalRef.

Check HTTP failures, top-level GraphQL errors and mutation-payload errors. HTTP 200 alone is not success. On unavailable capacity or changed prices, refresh the selection and obtain the customer's agreement to any change. On an authorization error, check the configured access with the administrator; do not switch to anonymous access. If delivery notes are unavailable, resolve the venue configuration rather than substituting another payment method.

Cancel a booking

graphql
mutation PartnerCancel($input: RequestCancelInput!) {
  requestCancel(input: $input) {
    request {
      id
      state
      externalRef
    }
    errors {
      key
      message
      messageTranslated
    }
  }
}
json
{ "input": { "id": "<returned cart accessToken>", "autoRefund": false } }

For an order completed only through fiscal delivery notes, cancellation creates a correcting delivery note. Your system remains responsible for refunding the guest. Agree cancellation and amendment rules with the venue; do not implement rebooking by blindly creating a second order after a timeout.

Ready to Get Started?

Book a free demo or reach out — we’d love to hear from you.