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
| Actor | Role in this flow |
|---|---|
| Partner / integrator | Owns the Flipdish App and its OAuth clients (Client ID / Secret Key). Requests access tokens and calls the API. |
| Customer | The 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 IdentityServer | Issues access tokens at https://api.flipdish.co/identity/connect/token and handles browser consent at /identity/connect/authorize. |
| Flipdish Webhook Service | Stores 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
- Create the OAuth app (and its OAuth client) on the partner side.
- Grab the Client ID and Secret Key.
- The customer (an Owner on their org) authorises the app against their org (browser consent).
- Request an access token (Client Credentials for automation, or exchange the authorise code for a user token).
- Resolve the customer's
orgId. - Create the v3 webhook subscription.
- Verify and test delivery.
Step 1 — Create the OAuth app
On the partner account in the Flipdish Developer Portal:
- Sign in at
https://portal.flipdish.com. - Click your avatar, then Developer tools.
- Open OAuth apps and click the red Add New button.
- 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) andGET /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-configurationIt 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}/redirecturisYou 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 apiAfter 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/authorizewith 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/tokencURL
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_tokenis a Bearer token used in theAuthorizationheader 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
orgIdWebhook 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-Agentheader is required on all API calls. - If you also need to scope the subscription to specific locations, list the org's properties to get
propertyIdvalues (p123format).
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}/subscriptionsAuth: 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.
Ownershipv3 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
| Header | Description |
|---|---|
X-Flipdish-Event-Type | Event type identifier (e.g. menu.published.v1) |
X-Flipdish-Event-Version | Event version (e.g. v1) |
X-Flipdish-Timestamp | ISO-8601 timestamp when the event was sent |
X-Flipdish-Signature | HMAC-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}/triggerThis 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).
| Purpose | Method & Path |
|---|---|
List OAuth clients under an App (optional oauthAppName filter) | GET /api/v1.0/{appId}/oauthclients |
| Create a new OAuth client | POST /api/v1.0/{appId}/oauthclients |
| Get a client by id | GET /api/v1.0/{appId}/oauthclients/{oauthAppId} |
| Update a client | POST /api/v1.0/{appId}/oauthclients/{oauthAppId} |
| Delete a client | DELETE /api/v1.0/{appId}/oauthclients/{oauthAppId} |
| Reveal a client's secret | GET /api/v1.0/{appId}/oauthclients/{oauthAppId}/secret |
| Get an access token for a client | GET /api/v1.0/{appId}/oauthclients/{oauthAppId}/accesstoken |
| List application names | GET /api/v1.0/{appId}/oauthclients/appnames |
| List redirect URIs | GET /api/v1.0/{appId}/oauthclients/{oauthAppId}/redirecturis |
| Add a redirect URI | POST /api/v1.0/{appId}/oauthclients/{oauthAppId}/redirecturis |
| Remove a redirect URI | DELETE /api/v1.0/{appId}/oauthclients/{oauthAppId}/redirecturis/{uriId} |
Notes:
appIdis the parent Flipdish App;oauthAppIdis the OAuth client (Client ID) within it. "App" here is older terminology but is still current in these routes — find yourappIdin 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
| Purpose | Method & 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 discovery | GET https://api.flipdish.co/identity/.well-known/openid-configuration |
| Manage OAuth clients (v1.0) | see Managing OAuth clients via the API above |
| List orgs | GET https://api.flipdish.co/orgManagement/orgs |
| Create webhook subscription | POST https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions |
| List webhook subscriptions | GET https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions |
| Update webhook subscription | POST https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions/{subId} |
| Delete webhook subscription | DELETE https://api.flipdish.co/webhooks/orgs/{orgId}/subscriptions/{subId} |
| Trigger test deliveries | GET 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
| Symptom | Likely cause / fix |
|---|---|
401 Unauthorized on API call | Token missing, malformed, or expired. Re-request a token. |
403 Forbidden creating a subscription | The authorising identity lacks the Owner / Managed Owner (Edit Webhooks) permission on the org. |
409 Conflict | A matching subscription already exists. Update it instead of creating a new one. |
| No webhooks arriving | Confirm the subscription is enabled, the callback URL is public and HTTPS, and any firewall allows Flipdish traffic. |
| Signature check failing | You are hashing the parsed body. Use the raw body and ${timestamp}.${rawBody}. |
Updated about 17 hours ago

