Skip to content
innovorder
⌘K

Event stream

Order Events

The Innovorder platform fires two order webhook events: orders.paid and orders.cancelled. The orders.paid event is triggered whenever an order is successfully placed and paid for. The orders.cancelled event is triggered whenever a previously paid order is cancelled. Both events deliver the exact same OmnichannelOrder payload structure, differing only in the status field (PAID vs CANCELLED). This is the primary mechanism to inject orders into external systems and to keep them synchronized with cancellations.

Kiosk "pay at the counter" orders

An orders.paid delivery does not always mean the guest paid on the ordering channel. A kiosk order placed to be settled at the counter carries the reserved key metadata.kioskPaymentState set to "NOT_PAID_AT_KIOSK". When the restaurant has no POS device receiving kiosk orders, that sale is recorded through a synthetic external payment, so it reaches you as orders.paid with status: "PAID" and a payments[] entry of type "ExternalPayment".

The key is written once at order creation and never changes afterwards: it says where the guest was expected to pay, not whether the money has been collected. Read it, not status, to know whether a kiosk order was paid at the kiosk. See the full rule in Orders > Create Orders.

Retry Policy

Your server must return an HTTP 200 OK response code with an empty body to acknowledge receipt. If not, we will resend the webhook based on an exponential backoff schedule.

Note: We do not retry requests if your server responds with 401 Unauthorized or 404 Not Found.

Event Structure

Every order webhook delivery is wrapped in the following envelope:

json
{
  "event_type": "orders.paid",
  "event_id": "c4d2261e-2779-4eb6-beb0-cb41235c751e",
  "event_time": 1427343990,
  "payload": { ... }
}
  • event_type: Either "orders.paid" or "orders.cancelled".
  • event_id: HMAC-SHA256 hash that uniquely identifies this delivery. Use it for deduplication.
  • event_time: Unix timestamp in milliseconds indicating when the event was generated.
  • payload: The complete OmnichannelOrder object described below.

Complete Payload Example

Below is a full, realistic example of an orders.paid webhook delivery showing all key fields:

json
{
  "event_type": "orders.paid",
  "event_id": "a3f8c2d1e9b74056ab12cd34ef567890abcdef1234567890abcdef1234567890",
  "event_time": 1711360200000,
  "payload": {
    "omnichannelOrderId": "98765432",
    "reportOrderId": 987654321,
    "orderUuid": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
    "brandId": 100,
    "restaurantId": 200,
    "customerId": 5001,
    "userId": null,
    "dailyOrderId": 42,
    "status": "PAID",
    "channelId": "2",
    "consumptionModeId": 1,
    "deviceId": "WEB-001",
    "deviceName": "Web Ordering",
    "seller": null,
    "ticketNumber": "A042",
    "tableName": null,
    "comment": null,
    "numberOfGuests": 1,
    "currency": "EUR",
    "startDate": "2026-03-25T12:30:00.000Z",
    "lastUpdateDate": "2026-03-25T12:32:00.000Z",
    "businessYear": 2026,
    "businessMonth": 3,
    "businessDay": 25,
    "service": 0,
    "shift": { "name": "Lunch" },
    "totalPriceWithTaxIncluded": 1850,
    "totalPriceWithTaxExcluded": 1542,
    "totalPriceDiscountedWithTaxIncluded": 1550,
    "totalPriceDiscountedWithTaxExcluded": 1292,
    "totalTax": 258,
    "totalDiscount": 300,
    "turnover": 1550,
    "totalAmountFree": 0,
    "totalItemFree": 0,
    "totalAmountCancelled": 0,
    "totalItemCancelled": 0,
    "items": [
      {
        "itemId": 5001,
        "parentItemId": null,
        "name": "Classic Burger",
        "type": "PRODUCT",
        "status": "VALIDATED",
        "quantity": 1000,
        "totalPriceWithTaxIncluded": 1200,
        "originalUnitPrice": 1200,
        "vatValue": 1000,
        "skuValue": "BUR-001",
        "free": false,
        "tags": [{ "value": "Burger" }],
        "categories": [{ "value": "Main Course" }],
        "discounted": false,
        "measurementUnit": "UNIT",
        "measuredQuantity": 1
      }
    ],
    "payments": [
      {
        "type": "Card",
        "label": "Visa",
        "quantity": 1,
        "value": 1550,
        "luncheonCode": null,
        "transactionId": null
      }
    ],
    "taxes": [
      {
        "taxRate": 1000,
        "totalTax": 258,
        "totalInclTax": 1550,
        "totalExclTax": 1292
      }
    ],
    "discounts": [
      { "name": "SUMMER10", "value": 300 }
    ],
    "fees": [
      {
        "name": "Delivery Fee",
        "taxRate": 2000,
        "quantity": 1,
        "amount": 350,
        "type": "Delivery"
      }
    ],
    "grants": [],
    "entranceFees": [],
    "loyaltyPrograms": [],
    "pricingRule": null,
    "customer": {
      "phone": "+33612345678",
      "email": "jean.dupont@example.com",
      "firstName": "Jean",
      "lastName": "Dupont",
      "badgeNumber": null,
      "disabledAt": null,
      "student": null
    },
    "restaurant": {
      "name": "Chez Yann",
      "contactPhone": "0147689047",
      "externalId": "SITE01_REST01",
      "address": {
        "streetNumber": "12",
        "route": "Rue de la Paix",
        "postalCode": "75002",
        "locality": "Paris"
      }
    },
    "deliveryAddress": {
      "phone": "+33612345678",
      "route": "45 Avenue des Champs-Elysees",
      "postalCode": "75008",
      "locality": "Paris",
      "streetNumber": "45",
      "additionalInfo": "3rd floor",
      "digicode": "4521",
      "apartmentNumber": "12B"
    },
    "photos": [],
    "cancellationReason": null,
    "cancellationReasonDescription": null,
    "metadata": null
  }
}

OmnichannelOrder Payload Reference

The payload contains the complete details of the order. All monetary values are expressed in cents (e.g., 1850 = 18.50 EUR) unless otherwise noted. Below is a comprehensive field-by-field reference grouped by category.

Core Identifiers

FieldTypeDescription
omnichannelOrderIdStringUnique identifier of the order.
reportOrderIdNumberNumeric ID used in reporting and exports.
orderUuidString (UUID)UUID v4 identifier of the order.
brandIdNumberUnique identifier of the brand.
restaurantIdNumberUnique identifier of the restaurant.
customerIdNumber | nullID of the customer who placed the order, or null for anonymous orders.
userIdNumber | nullID of the authenticated user (e.g., POS operator), or null.
dailyOrderIdNumberSequential order number for the current business day.
channelIdString (Enum)Ordering channel: "1" = Kiosk, "2" = Web, "5" = POS.
consumptionModeIdNumber (Enum)Consumption mode: 1 = Delivery, 2 = Take Away, 3 = Sit In, 4 = Drive.
deviceIdString | nullIdentifier of the device that created the order.
deviceNameString | nullHuman-readable name of the device.
sellerString | nullName of the seller/cashier, if applicable.
ticketNumberString | nullPrinted ticket number displayed to the customer (e.g., "A042").
tableNameString | nullTable name for sit-in orders, or null.
commentString | nullFree-text comment left by the customer.
numberOfGuestsNumberNumber of guests for the order.
currencyStringISO 4217 currency code (e.g., "EUR").

Timestamps

FieldTypeDescription
startDateString (ISO 8601)Timestamp when the order was created.
lastUpdateDateString (ISO 8601)Timestamp of the last modification to the order.
businessYearNumberFiscal year of the order.
businessMonthNumberFiscal month of the order (1-12).
businessDayNumberFiscal day of the order (1-31).
serviceNumberService period index (0-based).
shift.nameString | nullName of the shift (e.g., "Lunch", "Dinner").

Status

FieldTypeDescription
statusString (Enum)Order status: "PAID", "CANCELLED", or "VALIDATED".
cancellationReasonString | nullReason code for cancellation. Present only when status is "CANCELLED".
cancellationReasonDescriptionString | nullHuman-readable description of the cancellation reason.
metadataString | nullJSON-encoded metadata of the originating Web/Kiosk order, as sent on POST /orders (null when the order has no Web/Kiosk counterpart, e.g. a POS sale). The platform owns a single key inside it, kioskPaymentState, set to "NOT_PAID_AT_KIOSK" when a kiosk guest ordered to pay at the counter, which can happen while status already reads "PAID". Every other key is yours and is passed through untouched.

Pricing (all values in cents)

FieldTypeDescription
totalPriceWithTaxIncludedNumberTotal price including tax, before discounts.
totalPriceWithTaxExcludedNumberTotal price excluding tax, before discounts.
totalPriceDiscountedWithTaxIncludedNumberTotal price including tax, after discounts.
totalPriceDiscountedWithTaxExcludedNumberTotal price excluding tax, after discounts.
totalTaxNumberTotal tax amount.
totalDiscountNumberTotal discount amount applied to the order.
turnoverNumberNet revenue (total after discounts, tax included).
totalAmountFreeNumberTotal value of free (complimentary) items.
totalItemFreeNumberCount of free items in the order.
totalAmountCancelledNumberTotal value of cancelled items.
totalItemCancelledNumberCount of cancelled items in the order.

Item Object

FieldTypeDescription
itemIdNumberUnique identifier of the item.
parentItemIdNumber | nullID of the parent item for sub-items (e.g., menu steps). null for top-level items.
nameStringDisplay name of the product.
typeStringItem type (e.g., "PRODUCT", "MENU", "STEP").
statusStringItem status (e.g., "VALIDATED", "CANCELLED").
quantityNumberQuantity in thousandths (e.g., 1000 = 1 unit, 2500 = 2.5 units).
totalPriceWithTaxIncludedNumberTotal item price including tax (cents).
originalUnitPriceNumberOriginal unit price before any discount (cents).
vatValueNumberVAT rate in basis points (e.g., 1000 = 10.00%).
skuValueString | nullSKU code of the product.
freeBooleanWhether the item was given for free.
tagsArrayList of tag objects. Each has a value string.
categoriesArrayList of category objects. Each has a value string.
discountedBooleanWhether a discount was applied to this item.
reducedBoolean | undefinedWhether the item has a reduced price (e.g., pricing rule).
measurementUnitStringUnit of measurement (e.g., "UNIT", "KG").
measuredQuantityNumberHuman-readable quantity (e.g., 1 for 1 unit, 0.5 for 500g).

Payment Object

FieldTypeDescription
typeStringPayment type: "Cash", "Card", "EWallet", "Change", etc.
labelString | nullDisplay label for the payment method (e.g., "Visa").
quantityNumberNumber of payment units (typically 1).
valueNumberPayment amount in cents.
luncheonCodeString | nullLuncheon voucher code, if applicable.
transactionIdString | nullExternal transaction ID from the payment provider.

Tax Object

FieldTypeDescription
taxRateNumberTax rate in basis points (e.g., 1000 = 10.00%, 2000 = 20.00%).
totalTaxNumberTotal tax amount for this rate (cents).
totalInclTaxNumberTotal amount including this tax (cents).
totalExclTaxNumberTotal amount excluding this tax (cents).

Fee Object

FieldTypeDescription
nameStringName of the fee (e.g., "Delivery Fee").
taxRateNumberTax rate applied to the fee (basis points).
quantityNumberNumber of fee units.
amountNumberFee amount in cents.
typeStringFee type: "Delivery" or "Service".

Discount Object

FieldTypeDescription
nameStringName or code of the discount (e.g., "SUMMER10").
valueNumberDiscount amount in cents.

Grant Object

FieldTypeDescription
labelTicketStringLabel printed on the ticket for the grant.
amountNumberUnit grant amount in cents.
quantityNumberNumber of grant units.
totalAmountNumberTotal grant amount in cents (amount * quantity).

EntranceFee Object

FieldTypeDescription
labelTicketStringLabel printed on the ticket for the entrance fee.
amountNumberUnit entrance fee amount in cents.
vatNumberVAT rate applied to the entrance fee (basis points).
typeStringType of entrance fee.
totalTaxEntranceFeeNumberTotal tax amount for the entrance fee (cents).
quantityNumberNumber of entrance fee units.
totalAmountNumberTotal entrance fee amount in cents.

Customer Object

FieldTypeDescription
phoneString | nullCustomer's phone number.
emailString | nullCustomer's email address.
firstNameString | nullCustomer's first name.
lastNameString | nullCustomer's last name.
badgeNumberString | nullRFID/NFC badge number.
disabledAtString | nullTimestamp when the customer account was disabled, or null if active.
studentObject | nullStudent information object containing class, section, subSection, and studentNumber.
balanceInformationObject | nullE-Wallet balance info containing ewalletBalanceBeforeOrder and ewalletBalanceAfterOrder (cents).

Restaurant Object

FieldTypeDescription
nameStringRestaurant name.
contactPhoneString | nullRestaurant contact phone number.
externalIdString | nullExternal identifier for the restaurant (used for third-party integrations).
address.streetNumberString | nullStreet number of the restaurant address.
address.routeString | nullStreet name.
address.postalCodeString | nullPostal/ZIP code.
address.localityString | nullCity name.
address.countryString | nullCountry code or name.

Delivery Address

FieldTypeDescription
phoneString | nullContact phone number for delivery.
streetNumberString | nullStreet number.
routeString | nullStreet name.
postalCodeString | nullPostal/ZIP code.
localityString | nullCity name.
additionalInfoString | nullAdditional delivery instructions (e.g., "3rd floor").
digicodeString | nullBuilding entry code.
apartmentNumberString | nullApartment or unit number.

Note: Object Mapping for Students

For student account integrations, mapping specific fields is often required.

javascript
// Example JS extraction
const payload = event.payload;

const mapping = {
  badgeNumber: payload.customer.badgeNumber,
  registrationNumber: payload.customer.student.studentNumber, // Matricule
  accountId: payload.customerId, // ID Convive
  siteId: payload.restaurant.externalId.split('_')[0]
};

Note: The OmnichannelOrder payload is the same structure returned by GET /v1/orders. See Retrieve Orders for query parameters and filtering options.