Flavoso APIWebhooks and eventsFetching events

Fetching events

GET /events returns the same events as webhooks, for you to fetch: for POS systems without a public address and for catching up after an outage.

When to fetch instead of receive

  • POS without a public address: a program in the restaurant behind a router cannot receive webhooks. It fetches the events.
  • Catching up: after an outage, a switched-off webhook or a bug in your processing.
  • Reconciling: as a second path next to the webhook that makes sure nothing is missing.

Events are created for every active connection, even if your app has no webhook at all.

Fetching

GET /events returns the restaurant's events your connection may see, plus the app.* events of this connection, in ascending order, the oldest first, up to 30 days back. app.disconnected never arrives here: the connection's tokens end with the disconnection. You recognise a disconnection by a 401 and then invalid_grant on the refresh.

ParameterMeaning
afterAn event id (evt_…): only what came after it. Without after, it starts at the oldest kept event of the last 30 days, including events from before the connection. latest: no events, only the position after the newest one, to start a new connection. An id that this restaurant does not have (any more), for example because it is older than 30 days, continues with the oldest kept event and sets gap: true.
typesTypes separated by commas, with * for a whole group: order.created,menu.*.
limit1 to 100, default 50.

The response is a list with data, has_more, next_after and gap. next_after is the value for after to continue from there; it is always set, even when there were no events. gap: true means your after was no longer kept: events may have been missed, so resynchronise with GET /orders?updated_since=… and the other lists you use.

{
  "object": "list",
  "data": [
    {
      "id": "evt_2bf5a9b06b75442a9244e884a1acfc57",
      "object": "event",
      "type": "reservation.updated",
      "created_at": "2026-10-04T17:45:10.410Z",
      "api_version": "2026-10-08",
      "livemode": true,
      "tenant_id": "7d0e5b8a-3c41-4f2e-9a6b-1e2d3c4b5a69",
      "connection_id": "4b3f7c1e-9a2d-4e8b-b5f6-0c1d2e3f4a5b",
      "data": {
        "object": {
          "object": "reservation",
          "id": "9c8b7a6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
          "code": "R7K2Q9",
          "status": "confirmed",
          "starts_at": "2026-10-10T17:30:00.000Z",
          "duration_minutes": 120,
          "party_size": 4,
          "table_ids": [
            "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e"
          ],
          "source": "staff",
          "guest_name": "Alex Example",
          "note": "Window seat if possible",
          "created_at": "2026-10-04T12:00:00.000Z",
          "updated_at": "2026-10-04T12:00:00.000Z"
        },
        "previous_status": "pending",
        "changed": [
          "status"
        ]
      }
    }
  ],
  "has_more": false,
  "next_after": "evt_2bf5a9b06b75442a9244e884a1acfc57",
  "gap": false
}

The polling loop

  1. 1

    Where you are

    Read the after value you stored for this connection. For a new connection use latest: without after you would get the last 30 days, including events from before the connection.

  2. 2

    Fetch and process

    Fetch events and process each one. The processing detects duplicates by id.

  3. 3

    Move on

    Only once the whole page is processed, store next_after durably, per connection.

  4. 4

    Continue or wait

    If has_more is true, fetch the next page right away. Otherwise wait a few seconds.

# A new connection starts now: no events, only next_after. Store it for this connection.
# (Without after you would get the last 30 days, also events from before the connection.)
curl "https://api.flavoso.com/v1/events?after=latest" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Then always from where you stopped: after = next_after of the previous page.
# "gap": true means that after was no longer kept: resync with GET /orders.
curl "https://api.flavoso.com/v1/events?types=order.*,menu.*,store.*,app.*&limit=100&after=evt_2bf5a9b06b75442a9244e884a1acfc57" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Polling every 5 to 30 seconds fits easily into the rate limit of 120 requests per minute per connection.

A single event

GET /events/{id} returns one event, for example to look up an id from the webhook log.

curl https://api.flavoso.com/v1/events/evt_2bf5a9b06b75442a9244e884a1acfc57 \
  -H "Authorization: Bearer $ACCESS_TOKEN"

The same envelope

A fetched event is the same envelope as in the webhook: the same id, the same fields, the same data, frozen on the first read or send. If you use both, you detect duplicates by id. The signature only exists for webhooks; when fetching, the token proves who is asking.

Retention

Events are kept for 30 days. For anything older, catch up through the lists, for example GET /orders with updated_since. An after older than that is not an error: the response continues with the oldest kept event and says gap: true.

Still unclear? Write to support@flavoso.com and include the id from X-Request-Id. It lets us find your request in the log.