AGPLv3, all of it. The Android client, the server, and the wire protocol are all under that licence, and the server is the complete server -- no closed components, no stubs. It runs on Android 5.0 and up (minSdk 21), the floor most rugged and low-cost PoC handsets actually sit at. No audio is ever recorded or stored anywhere in the stack.
This page is for the person deciding whether to trust us with their radio traffic. It states what the system does, how a transmission actually gets from one radio to another, what it protects, what it does not protect, and what it costs.
Every hop is encrypted, but voice is not end-to-end encrypted: audio travels over DTLS-SRTP to a Cloudflare Realtime SFU, and an SFU must decrypt and re-encrypt each leg it relays, so Cloudflare, as the relay operator, is technically able to access audio in transit.
What is protected: audio is encrypted in transit on every leg, so no on-path observer can hear it. Our own servers, on Cloudflare Workers, carry only signalling, floor control, and provisioning -- media never touches them. Nothing in the stack records or stores audio, anywhere; there is no server-side buffer, archive, or transcript.
Each radio's private key never leaves the device; it is stored in the app's own private storage, not in the hardware keystore. The public key is the radio's identity -- there is no password to steal server-side.
Every control message in either direction is an Ed25519-signed envelope, and replay is blocked by a per-direction, strictly increasing sequence number that the signature itself covers. Every frame the server sends is signed too, with keys the client ships with, so server authenticity does not rest on the TLS layer alone. The roster, and the signal saying which radios are waiting to talk while another one is transmitting, are the exception: both keep those screens current, both are unsigned and display-only, and both carry no authority -- they cannot grant a floor or authenticate anyone.
One radio holds the floor at a time. The server decides who has it, taking contending requests one at a time, so two radios can never both be granted. The floor comes free when the transmitting radio releases it, or when a transmission runs past its length cap and is ended -- either way the channel is immediately open to the next request.
The audio codec is negotiated by standard WebRTC behaviour; in practice both ends land on Opus. We deliberately pin nothing -- there is no codec preference forcing and no SDP rewriting in the client or server. The send bitrate is picked once per transmission from the round-trip time and packet loss measured on the link, not re-evaluated mid-hold.
Loss concealment and jitter buffering are stock WebRTC NetEq, untuned; we widen the playout buffer to ride out bursty cellular links. If the link is bad enough that you would be talking into a hole, the press is aborted locally before the floor is requested, rather than granting you a floor nobody can hear.
A Google account is required: it is the only identity provider, and an unclaimed radio is refused, so it can neither talk nor listen.
The only usage record we keep is per-radio, per-day byte counts -- no content. A radio also sends hashed hardware identifiers, used only to block abuse, plus its device model details. Avatar images are the only stored media. The server keeps floor and talk-budget state only. Our operational logs record signalling events -- floor activity, radio codes, timestamps -- never audio.
None of it is sold, and none of it is shared beyond the providers that run the service itself -- Cloudflare, which hosts the servers and relays the audio and signalling, and Google, which handles sign-in.
RECORD_AUDIO.The radios OpenPOC is known to run on are the ones in our own test fleet: the Bin-Qi BQ889, BQ268 and BQ_V8, and the TELO TE390. That is a record of what we have actually run it on -- not a compatibility list, and not a guarantee about any other device.
If you build radio hardware or a ROM, you should not have to hardcode another vendor's proprietary broadcast to make the PTT key work. OpenPOC listens for a vendor-neutral pair that any ROM is free to adopt: poc.radio.ptt.down when the key is pressed, poc.radio.ptt.up when it is released.
Send them as implicit broadcasts. Do not set a component and do not set a package -- a targeted broadcast reaches one client only, which defeats the point of a shared name. No extras are required and none are read; the receiver dispatches on the action alone. Do not attach a receiver permission either: OpenPOC declares none, and a permission the client does not hold silently drops the broadcast.
The two are a matched pair. Every down must be followed by exactly one up on release -- including when the screen is off, when another app is in front, and during a call. Do not repeat the down while the key is held: one down per physical press, one up per physical release. A ROM that cannot guarantee the up must not send the down.
One limitation to know about, plainly: on Android 8 and above an implicit broadcast does not reach a manifest-declared receiver, which is why OpenPOC also registers the same filter at runtime. On those ROMs the pair only lands while the OpenPOC service is alive.
OpenPOC is free to use, and stays free. Relaying audio and signalling through Cloudflare is not free to run, so cost is bounded rather than unlimited: every account gets a 100 MB per day / 1 GB per month byte quota, and free-tier transmissions are held to a send-bitrate ceiling so bytes-per-minute stay predictable.
In talking time, that is roughly 9 hours a day, or about 90 hours a month, with one radio listening. The allowance is charged per listener, so it divides as more radios listen: about 45 hours a month with two listening, about 30 with three. Only the transmitting radio is charged -- listening costs nothing, and a radio that is merely connected costs nothing. These are estimates from the metering formula, not measured airtime.
You can run the whole backend yourself, with a real caveat: it targets Cloudflare's platform specifically. Self-hosting means your own Cloudflare account, your own Google OAuth application, and your own Cloudflare Realtime registration. The storage and media layers (D1, R2, Realtime) are proprietary Cloudflare services with no substitute in the repository -- there is no plain-VPS path today.
The entire repository -- client, server, protocol -- is licensed under the GNU AGPLv3. If you run a modified server for others, you owe them the source. That license covers the code, not the name or mark -- see Brand and press for what you may do with those.