OAuth and webhooks integrator guide

How a partner builds a Flipdish OAuth app, a customer authorises it, and the integrator creates a v3 webhook subscription so the customer's business events flow into the partner platform in real time

Introduction

Scenario: A partner builds an OAuth app once, a customer authorises it, and the resulting access lets the integrator create a v3 webhook subscription so the customer's business events flow into the partner platform in real time.

Audience: Integrators / developers building on the Flipdish API. Familiarity with HTTP, JSON, OAuth2, and HMAC signatures is assumed.

Actors

ActorRole in this flow
Partner / integratorOwns the Flipdish App and its OAuth clients (Client ID / Secret Key). Requests access tokens and calls the API.
CustomerThe Flipdish org whose data is being accessed. An Owner (or Managed Owner) on this org authorises the app and holds the permission to create webhooks.
Flipdish IdentityServerIssues access tokens at https://api.flipdish.co/identity/connect/token and handles browser consent at /identity/connect/authorize.
Flipdish Webhook ServiceStores subscriptions and delivers events to your callback URL.

Prerequisites

  • A Flipdish account for the partner (create one free at https://portal.flipdish.com/signup).
  • The customer org must exist on Flipdish, and the person authorising must have the Owner or Managed Owner role. These roles carry the Edit Webhooks permission, which is required to create a subscription.
  • A public HTTPS endpoint you control to receive webhook POSTs (a staging endpoint is recommended for first tests).
  • Ability to store the OAuth Secret Key and each subscription's shared secret securely.

Flow at a glance

  1. Create the OAuth app (and its OAuth client) on the partner side.
  2. Grab the Client ID and Secret Key.
  3. The customer (an Owner on their org) authorises the app against their org (browser consent).
  4. Request an access token (Client Credentials for automation, or exchange the authorise code for a user token).
  5. Resolve the customer's orgId.
  6. Create the v3 webhook subscription.
  7. Verify and test delivery.

Step 1 — Create the OAuth app

On the partner account in the Flipdish Developer Portal:

  1. Sign in at https://portal.flipdish.com.
  2. Click your avatar, then Developer tools.
  3. Open OAuth apps and click the red Add New button.
  4. Give the app a descriptive name (e.g. Example Integration) and press Create.

The App / OAuth client model

Flipdish nests credentials two levels deep, which matters once you manage them programmatically:

  • A Flipdish App (appId) is the integration record that holds your OAuth credentials, created under Developer tools → OAuth apps in the portal. It's a grouping for credentials only — not a tenancy level like an org, brand, or property, and distinct from both the consumer-facing Flipdish app and a Flipdish App Store listing.
  • Each App holds one or more OAuth clients (oauthAppId, i.e. the Client ID). Each client has its own Secret Key, access token, and set of redirect URIs.

You can create and manage these clients either in the portal (above) or programmatically via the v1.0 OAuthClients API (see Managing OAuth clients via the API below). The API is handy when the partner needs to provision or rotate clients at scale rather than clicking through the portal.

Step 2 — Get your credentials

Your app now has a Client ID and a Secret Key. You need both to request tokens.

  • The Secret Key is hidden by default. Use REVEAL SECRET KEY to view it.
  • 🔒 Treat the Secret Key like a password. It is used only between you and Flipdish to mint tokens. Never expose it to customers or ship it in client-side code.
  • You can also list your clients and read a client's secret programmatically: GET /api/v1.0/{appId}/oauthclients (list) and GET /api/v1.0/{appId}/oauthclients/{oauthAppId}/secret (reveal secret). See the OAuthClients reference below.

The IdentityServer discovery document lives at:

https://api.flipdish.co/identity/.well-known/openid-configuration

It returns the IdentityServer's current configuration as JSON — the token, authorize, and JWKS endpoints, the supported grant types and scopes, and the signing keys used to validate tokens. Read it at runtime rather than hard-coding these values, so your integration keeps working if an endpoint changes.

Step 3 — Customer authorises the app

For the partner app to access a customer's data, an Owner / Managed Owner on the customer org must grant the app access to that org. This is the browser-based step: the partner generates an authorise URL, the customer signs in and consents, and Flipdish redirects back to the partner with a token bound to that user.

Register your redirect URI(s) first. The authorise flow redirects back to a URL you control, so it must be pre-registered on the OAuth client:

POST https://api.flipdish.co/api/v1.0/{appId}/oauthclients/{oauthAppId}/redirecturis

You can list and remove them with GET and DELETE on the same path (see the OAuthClients reference below).

Generate the authorise URL. Build a link to the IdentityServer authorize endpoint and send the customer to it in their browser:

https://api.flipdish.co/identity/connect/authorize
  ?client_id=<oauthAppId>
  &redirect_uri=<your_registered_redirect_uri>
  &response_type=code
  &scope=openid api

After the user signs in and consents, Flipdish redirects to your redirect_uri and you exchange the result for a user-scoped token at https://api.flipdish.co/identity/connect/token. That token can act on the customer org's data limited by the user's role (e.g. Owner). 🔑

📘

Two paths, one decision

  • User-delegated (this step): browser-based consent at /identity/connect/authorize with redirect URIs, producing a token bound to a specific user. Use this when actions must run as, and be limited to, a named customer user (e.g. respecting that user's Owner permissions).
  • Client Credentials (Step 4): machine-to-machine at /identity/connect/token, no browser step, token represents the app itself. Use this for ongoing server-side automation once access is established.

Both are supported by the same OAuth client. Pick per use case; the webhook subscription in Step 6 just needs a valid token whose context carries the Edit Webhooks permission.

Step 4 — Request an access token (Client Credentials grant)

The token endpoint is:

POST https://api.flipdish.co/identity/connect/token

cURL

curl --request POST \
  --url https://api.flipdish.co/identity/connect/token \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'client_id=<your_client_id>' \
  --data-urlencode 'client_secret=<your_secret_key>' \
  --data-urlencode 'scope=api'

Python

import requests

response = requests.post(
    "https://api.flipdish.co/identity/connect/token",
    data={
        "grant_type": "client_credentials",
        "client_id": "<your_client_id>",
        "client_secret": "<your_secret_key>",
        "scope": "api",
    },
)
access_token = response.json()["access_token"]

Notes:

  • The response access_token is a Bearer token used in the Authorization header on every API call.
  • Tokens expire. Re-request one with your credentials when needed (cache until near expiry rather than requesting per call).
  • You can also generate a token from the Developer Portal (Request token) for quick testing, or fetch one for a client via GET /api/v1.0/{appId}/oauthclients/{oauthAppId}/accesstoken.

Step 5 — Resolve the customer's orgId

Webhook subscriptions are scoped to an org, so you need the customer's orgId (format org followed by numbers, e.g. org123).

curl --request GET \
  --url https://api.flipdish.co/orgManagement/orgs \
  --header 'Authorization: Bearer <access_token>' \
  --header 'User-Agent: MyIntegration/1.0'
  • 📌 A descriptive User-Agent header is required on all API calls.
  • If you also need to scope the subscription to specific locations, list the org's properties to get propertyId values (p123 format).

Step 6 — Create the webhook subscription (v3)

This step covers creating the subscription. For the full delivery contract, headers, event payloads, and every available event type, see Subscribe to Flipdish Events (v3).

Endpoint

POST https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions

Auth: Authorization: Bearer <access_token> (the token from Step 4). The caller's context must have the Owner / Managed Owner Edit Webhooks permission on the org.

📘

Ownership

v3 webhook subscriptions belong to the orgId, not to a user or OAuth app. This differs from legacy webhooks, which were tied to a user/OAuth app.

Request body

{
  "orgId": "org123",
  "callbackUrl": "https://webhooks.example.com/flipdish",
  "eventTypes": ["menu.published.v1", "sales.pos.status.updated.v1"],
  "secret": "<your_shared_secret_for_verification>",
  "propertyIds": ["p123", "p124"],
  "enabled": true,
  "createdBy": "my-integration"
}

Required fields: orgId, callbackUrl (must be HTTPS), eventTypes (at least one), secret, propertyIds.

A 200 returns the created subscription, including its subscriptionId. Store the subscriptionId and the secret.

Response codes to handle: 400 validation error, 401 unauthorised (bad/expired token), 403 insufficient permissions (missing Owner/Edit Webhooks), 409 subscription already exists, 500 server error.

Available event types

menu.published.v1, menu.validation.failed.v1, menu.item.snoozed.v1, menu.item.unsnoozed.v1, sales_channel.snoozed.v1, sales_channel.unsnoozed.v1, sales.pos.status.updated.v1, sales.delivery.status.updated.v1.

Step 7 — Receive and verify deliveries

Flipdish sends an HTTP POST with Content-Type: application/json to your callback URL when an event occurs.

Delivery headers

HeaderDescription
X-Flipdish-Event-TypeEvent type identifier (e.g. menu.published.v1)
X-Flipdish-Event-VersionEvent version (e.g. v1)
X-Flipdish-TimestampISO-8601 timestamp when the event was sent
X-Flipdish-SignatureHMAC-SHA256 signature t=<timestamp>,sha256=<hex> for verification

Response contract: return any 2xx once you have persisted the event. Non-2xx responses and timeouts are treated as failures and retried (at-least-once), so your handler must be idempotent.

Verify the signature using the raw request body, concatenated as ${timestamp}.${rawBody}, HMAC-SHA256 with your subscription secret, compared timing-safely. Do not JSON.parse before computing the signature.

import crypto from 'node:crypto';

function timingSafeEqual(a: string, b: string): boolean {
  const ab = Buffer.from(a, 'utf8');
  const bb = Buffer.from(b, 'utf8');
  if (ab.length !== bb.length) return false;
  return crypto.timingSafeEqual(ab, bb);
}

export function verifySignature(rawBody: string, timestamp: string, signature: string, secret: string): boolean {
  const payload = `${timestamp}.${rawBody}`;
  const expected = crypto.createHmac('sha256', secret).update(payload, 'utf8').digest('hex');
  return timingSafeEqual(expected, signature);
}

Step 8 — Test the subscription

Trigger synthetic test events to confirm your endpoint works before relying on real events:

GET https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions/{subId}/trigger

This sends test webhooks for every event type in the subscription and returns event IDs you can track. Alternatively, trigger a real event (e.g. publish a test menu) and confirm you receive the POST and that signature verification passes.

Managing OAuth clients via the API

If you prefer to provision and maintain OAuth clients programmatically rather than in the portal, use the v1.0 OAuthClients API. All routes are relative to https://api.flipdish.co, are scoped under a parent App (appId), and are authorised with an OAuth2 Bearer access token (scope=api).

PurposeMethod & Path
List OAuth clients under an App (optional oauthAppName filter)GET /api/v1.0/{appId}/oauthclients
Create a new OAuth clientPOST /api/v1.0/{appId}/oauthclients
Get a client by idGET /api/v1.0/{appId}/oauthclients/{oauthAppId}
Update a clientPOST /api/v1.0/{appId}/oauthclients/{oauthAppId}
Delete a clientDELETE /api/v1.0/{appId}/oauthclients/{oauthAppId}
Reveal a client's secretGET /api/v1.0/{appId}/oauthclients/{oauthAppId}/secret
Get an access token for a clientGET /api/v1.0/{appId}/oauthclients/{oauthAppId}/accesstoken
List application namesGET /api/v1.0/{appId}/oauthclients/appnames
List redirect URIsGET /api/v1.0/{appId}/oauthclients/{oauthAppId}/redirecturis
Add a redirect URIPOST /api/v1.0/{appId}/oauthclients/{oauthAppId}/redirecturis
Remove a redirect URIDELETE /api/v1.0/{appId}/oauthclients/{oauthAppId}/redirecturis/{uriId}

Notes:

  • appId is the parent Flipdish App; oauthAppId is the OAuth client (Client ID) within it. "App" here is older terminology but is still current in these routes — find your appId in the portal under Developer tools → OAuth apps (it identifies the app that contains your OAuth clients).
  • Redirect URIs are required for the browser-based authorise flow in Step 3. Register them before generating an authorise URL.
  • These are v1.0 endpoints using the OAuth2 authorization scheme (authorizationUrl: https://api.flipdish.co/identity/connect/authorize). The v3 webhook subscription in Step 6 is a separate, newer, org-scoped service. A single integration commonly uses v1.0 for auth / app management and v3 for webhooks.

Reference

Endpoints

PurposeMethod & Path
Get access token (token endpoint)POST https://api.flipdish.co/identity/connect/token
Authorise user (browser consent)GET https://api.flipdish.co/identity/connect/authorize
IdentityServer discoveryGET https://api.flipdish.co/identity/.well-known/openid-configuration
Manage OAuth clients (v1.0)see Managing OAuth clients via the API above
List orgsGET https://api.flipdish.co/orgManagement/orgs
Create webhook subscriptionPOST https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions
List webhook subscriptionsGET https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions
Update webhook subscriptionPOST https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions/{subId}
Delete webhook subscriptionDELETE https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions/{subId}
Trigger test deliveriesGET https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions/{subId}/trigger

Golden rules

  • Always use HTTPS; plain HTTP and unauthenticated calls fail.
  • Set a descriptive User-Agent on every request (e.g. MyIntegration/1.0).
  • Never expose the Secret Key; keep the subscription secret server-side.
  • Cache tokens until near expiry; re-request when needed.
  • Make webhook handlers idempotent and fast (offload heavy work to a queue).

Troubleshooting

SymptomLikely cause / fix
401 Unauthorized on API callToken missing, malformed, or expired. Re-request a token.
403 Forbidden creating a subscriptionThe authorising identity lacks the Owner / Managed Owner (Edit Webhooks) permission on the org.
409 ConflictA matching subscription already exists. Update it instead of creating a new one.
No webhooks arrivingConfirm the subscription is enabled, the callback URL is public and HTTPS, and any firewall allows Flipdish traffic.
Signature check failingYou are hashing the parsed body. Use the raw body and ${timestamp}.${rawBody}.

What’s Next