the heart
Webex Meetings
Every appointment is a real Webex meeting through the Meetings API. Browser-direct join, a trial call beforehand, live meeting status in the agenda.
ADR 0017Open source · EUPL-1.2 · WCAG 2.2 AA
OpenPlanner is the scheduler for video calls on Webex. A participant picks a free time on a page in your branding, gets a confirmation with a calendar invite and joins from the browser. No account, no app, no login. Your team receives the call at the right moment in Webex Contact Center, and personal data is gone 24 hours after the call.
A real appointment page, without the real appointment. Everything works by keyboard. Nothing is stored or sent.
Two tracks through the product: what the participant experiences, from picking a time to the call, and what the team and the administrators hold, from the organisation to the agent who picks up.
Recorded by the pipeline at release v0.4.0-rc.86 (2026-11-01), against the real application with test data: from a participant booking to the agent who picks up the call.
No sound; the captions describe what is shown. The steps are also in the tour above as text.
A team is a group: opening hours, staffing per block, holidays, exceptions, texts and branding. All in the admin portal, no code.
Every group has its own page on your domain. Or you place the calendar as a Web Component right inside your own site.
As a work item in Webex Contact Center, or in OpenPlanner's own queue where staff with the agent role pick up the call themselves. Chosen per group.
Built for teams that serve many people by video: customer service, advisory desks, intake, helpdesks.
Month calendar, free times, three fields. Move or cancel through a personal link. Works without JavaScript and fully by keyboard.
Every appointment gets its own Webex meeting. The participant joins from the browser, no app or account; your team joins as cohost.
The call goes to the queue in Webex Contact Center as a work item. The first free agent picks up. Nobody has to watch a calendar.
A group that is open can offer a direct call, with a cap on the number waiting and honest queue feedback.
One installation, several organisations, each with its own groups, branding, texts and identity provider. Roles and scope come from your IdP; one authorisation matrix decides what everyone sees and does.
A group is a skill with a subject, a team and a queue. The participant never picks a person; the first free agent picks up. One agent can be in several groups.
Personal data lives only on the booking and is gone 24 hours after the call. Never in logs, URLs, jobs or error messages.
Colours, logo and font per organisation. Every text editable in the portal. Dutch and English out of the box, more locales possible.
Confirmation, change, reminder and cancellation by email (with .ics) and SMS. SPF, DKIM and DMARC are checked for you.
Admins follow every call live, plan staffing per block and close a group in one action when needed, after which participants pick a new time themselves.
OpenPlanner is not a video platform and not a contact center. It is the scheduler that ties together the Webex products you already have, and keeps every other system behind a clean adapter.
the heart
Every appointment is a real Webex meeting through the Meetings API. Browser-direct join, a trial call beforehand, live meeting status in the agenda.
ADR 0017dispatch
Five minutes before the start the call becomes a work item in the team's queue, carrying only a first name, the join link and routing attributes. Webex Contact Center is the first integration; the layer behind it is provider-neutral, and a group can also work without a contact center through OpenPlanner's own queue.
ADR 0018alerts
A bot tells administrators in Webex when the integration falters, a queue fills up or a check fails. Never personal data, always a link to the portal.
ADR 0082OIDC
Administrators sign in through your own IdP (PingFederate, Keycloak, Entra ID, any OIDC provider), per organisation, with roles from the claims.
ADR 0083channels
SMTP and a provider-neutral SMS gateway per organisation. Texts per channel and per group editable in the portal.
ADR 0022machine API
A contact-center bot (such as the Webex AI Agent) asks for free times and books on the participant's behalf through an API with scopes and per-client keys.
docs/agent-api.mdNot because of a list of ticks, but because of how it is built: a core without a framework, every external service behind a port, and rules the pipeline enforces instead of a reviewer.
One server you manage yourself, two containers and one database. Everything outside it is a system you already have, connected through an adapter.
Containers run without root on a read-only filesystem.
Simplified. The full plate, with every flow and the decision behind it, is in the repository. System context and containers
Pure Python, no framework, ≥ 90% test coverage
VideoProviderWebex MeetingsContactCenterDispatcherWebex Contact Center, own queueMessageSenderSMTP, SMS gatewayIdentityProviderOIDC: PingFederate, Keycloak, Entra IDEventSinksyslog, Splunk, ElasticTransactionSQLiteAnother contact center or video platform is a new adapter behind an existing port. The domain, the screens and the texts do not change.
Layers, types, rights and privacy rules are checked on every pull request. What does not hold never reaches main.
Rights, settings and the portal's structure each live in one place; their documentation is generated from the code.
More than eighty ADRs with context, choice, settings and build status. An architect reads why things are the way they are, and what does not run yet.
Proxy, web and worker on one VM with SQLite in WAL mode. Readers do not block each other, a booking is a few rows under a write lock, several workers share the job queue. One installation serves many organisations. Scaling is vertical first: more CPU, memory and a faster disk.
The design target is about 80 bookings per working day. ADR 0036 records when we go further: above 2,000 bookings per working day, at a p95 above one second, with a stricter availability requirement, or when a client requires managed Postgres.
Postgres with row locks, several web instances behind the proxy, workers on any host and shared storage for rate limits. All database access already sits behind one port in one adapter module, so the domain, the routes and the screens stay the same.
ADR 0036: scaling envelope and the path beyond one VM
Role × permission × level in one place. Every admin route declares one permission, every query filters by scope, every denial is in the audit log. No exception for superusers.
Every query filters by the role's scope; an organisation admin never sees another organisation, a group admin never another group. The same matrix drives the menu, the buttons and the API.
OIDC with PKCE, state and nonce, asymmetric algorithms only, eight-hour session. No local accounts, no break-glass login, on purpose.
Logins, changes and denials as structured events, to syslog, Splunk or Elastic. Without personal data.
Images signed keyless with cosign, an SBOM (CycloneDX) on every release, CVE scans with trivy and grype, hashes in the lockfile. The installation verifies the signature.
mypy strict, ruff, bandit, semgrep, gitleaks, mutation testing, schemathesis, OWASP ZAP, Playwright with axe-core and Lighthouse. Everything blocks the merge.
Two containers, one SQLite file, HTTPS with automatic certificates or your own proxy. Updates and release channel from the portal. The backup is one file.
WCAG 2.2 AA on every public page, in every language, tested again on every pull request. No time limits, no CAPTCHA puzzles.
Every threshold, limit and period is a setting with a class, a default and bounds. What is a protection cannot be switched off from the portal.
What we claim is in the code, in a test or in the pipeline. For each standard you see how far it is and where the evidence is.
| Standard | What it means | Status | Evidence |
|---|---|---|---|
| WCAG 2.2 AA (EN 301 549) | Keyboard flow, axe-core with zero violations and Lighthouse ≥ 95 on every public page, in every language, on every pull request. Manual audit with NVDA and VoiceOver before go-live. | tested in CI | e2e/pages.json |
| NL Design System | Colours, type and spacing of the participant pages come from NL Design System tokens; organisations adjust them within those tokens. | built in | docs/widget.md |
| HTTPS, HSTS and NCSC TLS guidelines | HTTPS with HSTS (Forum Standaardisatie comply-or-explain list), TLS 1.2 and 1.3 per the NCSC guidelines. A check in CI and internet.nl in the go-live checklist are planned. | partly | ADR 0086 |
| SPF, DKIM and DMARC | Comply-or-explain standards for email. The portal checks each organisation's sender domain and shows what is missing. | built in | ADR 0073 |
| OpenID Connect | Administrators sign in through their own identity provider: PKCE, state and nonce, asymmetric algorithms only. Tested against Keycloak, PingFederate in production. | built in | ADR 0013 |
| OpenAPI 3 | The API specification is generated from the code, compared on every pull request and attacked with schemathesis. | tested in CI | docs/api.md |
| GDPR and privacy by design | Personal data only on the booking, purged 24 hours after the call, never in logs. The data-flow diagram is the input for the organisation's DPIA. | built in | docs/dataflow.md |
| Building blocks for the BIO | An audit log of every change and denial, security events on attacks, an auditor role and export to your log platform. The BIO itself remains the organisation's responsibility. | built in | ADR 0053 |
| security.txt (RFC 9116) | A fixed reporting point for vulnerabilities on every installation, with SECURITY.md in the repository. | built in | SECURITY.md |
| Software supply chain | Every image signed keyless with cosign, a CycloneDX SBOM with every release, CVE scans with trivy and grype, dependencies pinned with hashes. | tested in CI | ADR 0006 |
| Standard for Public Code | EUPL-1.2, a licence per file through REUSE, decisions public as ADRs, and a publiccode.yml that CI validates and every release updates. The listing on developer.overheid.nl is still open. | partly | publiccode.yml |
| Penetration test | An external penetration test before go-live and yearly after. Until then: OWASP ZAP on every merge and abuse cases as tests. | planned | docs/threat-model.md |
OpenPlanner is published under the EUPL-1.2, the open-source licence of the European Commission. Not only the code is open: every design decision sits in the repository as an ADR, with what has been built of it.
Use, change and distribute it, commercially too. Compatible with GPL, AGPL and most European procurement rules.
More than eighty decisions, each with context, choice, settings and build status. You read why things are the way they are, and what does not run yet.
A threat model per flow, an abuse case per endpoint, each with a test. Responsible disclosure through SECURITY.md.
Issues, discussions and pull requests on GitHub. A CLA keeps the licence clean. Every PR answers the same seven review questions.
Straight from GitHub: every open step of a decision is an issue. What is listed here is decided but not built yet.
72 open steps in 33 decisions
Every merge to main becomes a release candidate on the rc channel. A release is a signed image, an SBOM and this website, in one train.
Latest release
Latest release candidate
All releases on GitHub Installation guide
On every merge to main and on every release this page is rebuilt with the releases of that moment. Your browser never talks to GitHub, which keeps this site free of third parties. This page was built from commit 61a1a964a390.
One install script, a Let's Encrypt certificate and a demo group. Or start locally with docker compose and walk the portal as every role.