CheckCourt

CLI for local development

Log in, forward webhooks and extensions from your sandbox club to localhost, trigger test events and read delivery logs

The checkcourt CLI connects the app running on your machine to your developer organisation's sandbox club. You do not need a public URL, a tunnel or a deployed webhook endpoint: CheckCourt hands webhook deliveries and extension requests for the sandbox to the CLI, and the CLI replays them against localhost.

checkcourt login
checkcourt listen --app demo-app --forward-to http://localhost:4000
checkcourt trigger booking.created --app demo-app
checkcourt logs --app demo-app --follow

The relay only ever serves the sandbox club of the organisation that owns the app. Installations in real clubs keep receiving deliveries at your production URLs, whether or not a CLI is connected.

Install

The CLI is open source at CheckCourt/cli. It needs Node.js 20 or later. Install it globally from npm:

npm install -g @checkcourt/cli
checkcourt help

To run it without a global install, use npx @checkcourt/cli help. Release notes are in the repository's changelog.

By default the CLI talks to https://app.checkcourt.de. Point it at another platform with CHECKCOURT_HOST or --host.

Prerequisites

  • You are a member of a developer organisation that has a sandbox club. See Apps.
  • Your app is installed in the sandbox club.
  • You have the app's signing secret, which every app has from its creation on (developer portal → your app → Webhooks, card Signatur-Secret).
  • For webhooks, the app has a webhook URL on the same tab. It may be your future production address; while you listen, the sandbox's deliveries go to the CLI instead. Only https URLs are accepted in production. Declarative extensions are forwarded with the URLs from your manifest, no webhook URL needed.

Log in

checkcourt login

The CLI uses the OAuth 2.0 Device Authorization Grant (RFC 8628). It prints a code such as KCNR-RQRR and opens /developer/cli in your browser. Check that the code matches, then confirm with Anmelden bestätigen. The page lists the organisations the login will work for. Meanwhile the CLI polls until you confirm, then stores the token in ~/.checkcourt.json.

  • The token starts with ccd_, is valid for 30 days and is stored hashed on the platform.
  • It only allows the developer commands on this page, for the apps of organisations you are a member of. It cannot call the REST API and never carries support rights.
  • Use --no-browser on machines without a browser and open the printed URL elsewhere.
  • For CI, set CHECKCOURT_CLI_TOKEN instead of logging in.

Your logins are listed in the developer portal under Organisation → CLI-Sitzungen, where you can end any of them. checkcourt logout ends the current one.

Forward requests: listen

checkcourt listen --app demo-app --forward-to http://localhost:4000
OptionMeaning
--appSlug or id of your app
--forward-toOrigin of your local app
--webhook-pathPath for webhooks. Default: the path of the app's webhook URL
--extension-pathPath for all extension requests. Default: the path of each extension URL in the manifest

Without the path options, the CLI keeps the paths of your production URLs. A webhook URL https://demo.example/api/checkcourt/webhooks and a manifest extension https://demo.example/api/checkcourt/extensions/dashboard-widget become http://localhost:4000/api/checkcourt/webhooks and http://localhost:4000/api/checkcourt/extensions/dashboard-widget, so your app's real routes handle them.

Every request prints one line: time, kind, event type or extension point, the status your app returned and the duration.

14:02:11  webhook    booking.created         -> 200  41 ms  http://localhost:4000/api/checkcourt/webhooks
14:02:15  extension  dashboard.widget        -> 200  12 ms  http://localhost:4000/api/checkcourt/extensions/dashboard-widget
14:02:20  webhook    booking.cancelled       -> fehler ECONNREFUSED  3 ms

Press Ctrl+C to stop. If the connection drops, the CLI reconnects with exponential backoff (up to 30 seconds). It gives up when the login is invalid, the session was ended in the portal, or the app is not yours.

What is forwarded

RequestForwarded while listeningTimeout
Webhook deliveries to the sandbox installation (club and member installations)Yes10 s
Declarative extensions (render and actions)Yes2 s
iframe extensionsNo, the browser loads them directly. Use a http://localhost URL, see below
Deliveries to real clubsNever

Requests carry exactly the headers and body production would send, including CheckCourt-Signature, CheckCourt-Event-Id, CheckCourt-Installation-Id and, for extensions, CheckCourt-Context. Signatures use your app's webhook secret, so verification code works unchanged:

import { SIGNATURE_HEADER, verifyWebhook } from "@checkcourt/sdk";

export async function POST(request: Request) {
  const event = await verifyWebhook({
    secret: process.env.CHECKCOURT_WEBHOOK_SECRET!,
    rawBody: await request.text(),
    signatureHeader: request.headers.get(SIGNATURE_HEADER),
  });
  // ...
  return new Response(null, { status: 200 });
}

Webhook deliveries keep their normal retry schedule: if your local app is down or returns an error, the delivery is retried later, through the CLI if it is still listening and over HTTP otherwise. Deliveries that went through the CLI are marked via: "cli" in checkcourt logs.

localhost URLs in the sandbox

Two kinds of addresses may point at http://localhost or http://127.0.0.1 even on the production platform:

  • iframe extensions in the manifest (for example app.settings), and
  • OAuth redirect URIs.

They only take effect in your organisation's sandbox club. Real clubs never load such an iframe, and the consent screen only offers the sandbox club when the redirect URI is local. Declarative extensions and the webhook URL must stay https; locally they reach you through listen.

A version or listing that still contains local addresses cannot be submitted for review. Upload a version with public https URLs and remove local redirect URIs first, see Review and marketplace.

Trigger test events: trigger

checkcourt trigger booking.created --app demo-app
checkcourt trigger --list

trigger makes a real change in your sandbox club through the same code paths a club member would use, so the event is produced by the normal emission and delivered like any other:

EventWhat happens
booking.createdBooks the next free court for an invented sandbox member
booking.cancelledCancels an upcoming booking (books one first if there is none)
member.updatedChanges an invented member's phone number
court.lockedLocks a court that is not locked
court.unlockedUnlocks a locked court (locks one first if needed)
app.settings_updatedReports changed settings for your sandbox installation without changing them

The delivery starts right away instead of waiting for the next worker run. Your app only receives events it subscribes to in the manifest and has the scopes for.

Delivery logs: logs

checkcourt logs --app demo-app
checkcourt logs --app demo-app --follow

Shows the most recent webhook deliveries to your app's sandbox installations: time, event type, status, attempts, whether the last attempt went through the CLI, and your app's HTTP status or the error. --follow keeps polling and prints changes. Deliveries to real clubs are in the developer portal under your app → Webhooks.

How routing works

listen keeps a WebSocket connection to the platform open, authenticated with your CLI token. When CheckCourt is about to call your app for an installation in your organisation's sandbox club and a listen session for the app is connected, it sends the request through that connection instead of over HTTP. The CLI calls your local app and returns the response, and the platform waits with the normal timeout. Without a connected session, requests go over HTTP as usual.

With several listen processes for the same app, the most recently started one receives the traffic.

Limits

  • Sessions send a heartbeat every 30 seconds. Without one for 90 seconds, routing falls back to HTTP; after 30 minutes the session is ended.
  • Ending a CLI session in the portal, logging out, or leaving the organisation ends the relay at the next heartbeat.
  • Timeouts are the production ones: 10 seconds for webhooks, 2 seconds for declarative extensions. A slow local app fails the same way it would in production.
  • Responses larger than 64 KB fail for extensions as in production; webhook response bodies are stored truncated.

API commands

Besides the app development commands, the CLI is a small example client for the REST API. It uses a personal API key (ck_user_…, see Which key can do what?) instead of checkcourt login:

export CHECKCOURT_API_KEY="ck_user_..."
checkcourt tenants                       # your clubs and their ids
export CHECKCOURT_TENANT_ID="<club id>"

checkcourt slots --date 2026-10-14 --duration 60
checkcourt bookings
checkcourt book --court 1 --date 2026-10-14 --start 10:00 --end 11:00 --guest "Max"
checkcourt cancel <booking id>

Run checkcourt without arguments to start the interactive terminal app: it asks for your key once, then lets you pick a club, browse free slots, book and cancel.

Configuration

VariableMeaning
CHECKCOURT_HOSTPlatform origin for login, listen, trigger and logs. Default https://app.checkcourt.de. Also --host
CHECKCOURT_CLI_TOKENDeveloper token for CI instead of checkcourt login
CHECKCOURT_API_KEYPersonal API key for the API commands
CHECKCOURT_TENANT_IDClub id for club-scoped API commands
CHECKCOURT_BASE_URLAPI base URL for the API commands. Default https://app.checkcourt.de/api/v1

Environment variables override the config file ~/.checkcourt.json.

Security

  • The CLI token is bound to the person who confirmed it in the browser and only works for organisations that person currently belongs to.
  • Only the sandbox club of the app's own organisation is ever relayed. There is no way to route deliveries of a real club to a developer machine.
  • Request contents are the sandbox's invented data. Do not copy real member data into your sandbox.
  • Relay sessions, CLI logins and triggered events are written to the audit log.

On this page