AppSolution Services GmbH
Back to overview
7 min readby Ming Pham

WhatsApp as a Club Backend: How a Football Club Runs Training Sessions via Polls

A technical case study: how we integrated WhatsApp polls, automatic member capture and squad announcements directly into a club's software for FC Zugersee – including architecture, data flow, and the pitfalls of an unofficial WhatsApp connection.

  • Software Development
  • WhatsApp Integration
  • Next.js
  • Case Study

In short

Clubs live on WhatsApp – not in apps nobody installs. For FC Zugersee, we plugged in exactly there: training organisation runs entirely through a WhatsApp group, but behind the scenes a Next.js application orchestrates and records everything. An admin starts a weekly poll with one click, the players respond as usual right inside WhatsApp, and every vote lands automatically in the database – complete with attendance tracking, automatic member capture, and a scoring system for squad selection.

What makes this interesting isn't the frontend – it's the bridge between a cloud app and WhatsApp, a service that has no open door for this kind of thing. This case study shows how that bridge is built.

The problem: the software nobody opens

Most club tools fail at the same point: they require every member to install yet another app, log in, and stay active there. In practice, that never happens. Communication keeps flowing through the WhatsApp group, and the shiny app is abandoned.

So the requirement for FC Zugersee was clear:

Players should have to learn nothing new. They reply in WhatsApp like always. The organisation, the tracking, and the analysis happen invisibly in the background.

That shifts all the complexity away from the user interface and into the integration. And that's exactly where the real engineering effort lives.

The architecture from above

At the core is a connection to WhatsApp via the Evolution API – a self-hosted interface built on top of the open-source Baileys protocol. It runs – together with the tunnel client cloudflared – as a Docker container on a lean host. In our case that's a Mac mini at the clubhouse; thanks to containerisation, though, the exact same stack would run just as well on a cloud VPS. Because the interface shouldn't be directly reachable from the internet for security reasons, it's securely exposed through a Cloudflare Tunnel.

Architecture overview: a Next.js app and PostgreSQL on Vercel send polls and squads to a Cloudflare Tunnel on a Mac mini at the clubhouse, which reaches the Evolution API. The Evolution API posts messages into the WhatsApp group and reports cast votes back to the app via webhook.

Four deliberate architectural decisions are baked in:

  • Cloud app on Vercel for the actual club software (admin area, database, public website). Scales by itself, costs almost nothing when idle.
  • Evolution API self-hosted, because the official WhatsApp Business API is too expensive and too bureaucratic for an amateur club (Meta verification, template approvals, minimum volumes).
  • Everything in Docker containers (Evolution API + cloudflared) that encapsulate the WhatsApp session. For us this runs on a cheap Mac mini at the clubhouse that holds the session around the clock – but the same container stack lifts onto a cloud VPS without changes.
  • Cloudflare Tunnel instead of an open port, so the Mac mini is never directly reachable from the internet, while the cloud app can still talk to it.

The data flow: two directions

The integration works in both directions – that's the key to understanding it.

Outbound: the app talks to the group

When a coach clicks "Start poll" in the admin area, the app assembles a multiple-choice poll for the entire training week – one answer option per training session (e.g. "Tue 23.04. 19:00 · Sportplatz Herti"). This poll is posted into the WhatsApp group via the Evolution API:

POST /api/admin/training-weeks/[id]/poll
   → sendPoll(group, "Football Training KW 17", [options], multiSelect)
   → Evolution API → WhatsApp group

The returned message ID of the poll is stored in the database. Later, it's the crucial key for mapping incoming votes back to the right training.

The squad announcement works the same way: once the coach has assembled the team, the app posts the final list as a formatted text message. A nice detail here – the selected players are pinged via a silent mention (mentioned): they get a push notification even if the group is muted, without ugly @4179… markers cluttering the visible text.

Inbound: the group talks to the app

This is where it gets interesting. As soon as a player taps an answer option in WhatsApp, the Evolution API fires a webhook to the cloud app. This endpoint is the real nerve centre of the integration:

Webhook processing flow: first the secret is checked – without a valid secret it returns 401 and stops. Otherwise the poll ID is mapped to the training week. If the sender is a known member, attendance is recorded; if unknown, the member is auto-created from number and name. Finally the reliability score is recomputed.

Step by step, here's what happens:

  1. Authentication – The webhook checks a secret token. Without a valid secret, the request is rejected with 401. This is the only thing preventing someone from injecting fake votes.
  2. Mapping – Using the stored poll ID, the vote is mapped to the correct training week (and the correct session).
  3. Member recognition – The sender is matched to an existing member via their WhatsApp identifier or phone number. If the person isn't on file yet, the member is created automatically – from the phone number and WhatsApp display name. Nobody has to maintain members by hand.
  4. Recording – The yes/no response is saved per training, including a timestamp (important for the first-come-first-served selection) and withdrawal markers for late cancellations.
  5. Scoring – Afterwards, the member's reliability score is recomputed.

All of this happens in a fraction of a second, while the player simply tapped something in WhatsApp.

The interesting pitfalls

Clean theory meets a very messy reality here. These details are what separate a weekend experiment from a production integration.

1. The identity crisis: @lid instead of a phone number

For privacy reasons, WhatsApp has started to no longer hand out the real phone number in groups, but rather a group-internal, anonymous identifier (@lid). A vote therefore often arrives without a phone number. The app first has to resolve that anonymous identifier into a real number via the group's participant list – and only once per webhook call, cached, so it doesn't hammer the API. If an identifier can't be resolved at all (a fully private participant), the vote is deliberately dropped cleanly rather than creating a junk record.

2. Every Evolution version speaks differently

Depending on the WhatsApp client and Evolution version, poll responses arrive in at least four different data formats – sometimes with a counter, sometimes with a voter list, sometimes as a nested object. The parser is therefore deliberately defensive and accepts all known variants. Whatever it can't decode isn't silently swallowed but logged into a dedicated table (whatsapp_webhook_failures) – so silent failures become visible and debuggable.

3. Always "200 OK" – even on failure

The webhook deliberately always responds with success, even when something goes wrong internally. The reason: if it returned an error, Evolution would redeliver the same message endlessly and build up a queue. So errors are caught and logged internally, but acknowledged immediately to the outside.

4. Member reconciliation in both directions

On top of the automatic creation, there's an explicit sync: the app compares the current group participant list with the member roster and proposes who should be created, reactivated, or deactivated. Crucially, members with a special role (committee, kit manager, login account) are protected from automatic deactivation – the software makes no hasty decisions about people here.

Takeaways

This integration is a good example of how the right architecture starts where the users already are – not where it's technically most convenient.

  • Meet users in their channel. A WhatsApp poll has a response rate that any standalone app can only dream of.
  • Complexity shifts, it doesn't disappear. Keep the frontend simple and you pay the price in the integration – with identity resolution, defensive parsing, and failure logging.
  • Unofficial interfaces need care. WhatsApp offers no official, cheap API for cases like this. A self-hosted solution is feasible and robust, but it demands deliberate decisions around security (secret, tunnel instead of an open port) and operations (a container host that holds the session around the clock).
  • Make the invisible visible. Every dropped vote, every unresolvable participant is logged. In an integration with this many foreign variables, traceability isn't a luxury – it's a prerequisite.

Conclusion

For FC Zugersee this means, concretely: the coach clicks once, the players respond in WhatsApp as always – and behind the scenes a complete, analysable picture of attendance, reliability, and selection takes shape. No extra app, no new login, no training overhead.

If you need a similar bridge between your existing communication channels and a clean software solution – whether WhatsApp, Telegram, or another system – book a free initial consultation. We'll look at where your users already are and build the integration to reach them there.