The TAK protocol is the wire contract ATAK, WinTAK, iTAK and TAK Server use to move Cursor on Target (CoT) events. Version 0 ships the event as plain XML; version 1 as a length-framed protobuf message about half the size. The same payload rides three transports: serverless UDP multicast on the local mesh, persistent TCP/TLS streams to TAK Server (port 8089 by default), and server-to-server federation (9000/9001).
If you are integrating a sensor, drone or C2 system with the TAK ecosystem, the distinction matters immediately: CoT is the data model — <event>, <point>, <detail> — while the TAK protocol defines how those bytes are framed, versioned and negotiated on each transport. The XML schema itself is covered in our Cursor on Target format guide and the CoT schema deep dive; this article is the other half, the wire.
CoT vs the TAK protocol: data model versus wire contract
Keep three layers separate — the ecosystem documentation routinely blurs them:
- The CoT event model. One XML document per event: type code, UID,
time/start/stale, a WGS-84 point, an extensible<detail>. The semantic content, identical across transports. - The encoding (TAK protocol version). Version 0 encodes the event as XML text; version 1 as a protobuf
TakMessage. Both carry the same events. - The transport. UDP multicast on the mesh, a persistent TLS stream to TAK Server, or a federation link between servers — each frames the payload differently.
The protocol description distributed with the TAK products sets two ground rules: a client that sends version V can also decode version V, and every client must still decode version 0 XML. That is why mixed fleets keep working — everything degrades gracefully to XML.
TAK protocol version 0: plain CoT XML
Version 0 ran the ecosystem for its first decade and is still the fallback everywhere. It behaves differently on each transport:
- Mesh SA (multicast). Situational-awareness announcements are UDP datagrams sent to the well-known multicast group
239.2.3.1:6969— one CoT XML event per datagram, no server involved. Every device that joined the group receives every event, and there is no retransmission: a lost datagram is a lost position report. This is the mode behind serverless blue force tracking on a squad Wi-Fi or MANET segment. (ATAK multicasts GeoChat on a separate group,224.10.10.1:17012, which TAK Server can bridge as an input.) - Directed messages. Messages to one recipient use a short-lived TCP connection: connect, send one event, disconnect. An ATAK device listening for direct peers accepts them on TCP port 4242 — handy for injecting an event into a single handset with no server.
- Streaming to TAK Server. A persistent connection (usually TLS) carries a head-to-tail stream of XML events, each prefaced by an XML declaration and a newline. The receiver gets no length prefix and no delimiter: it splits the stream by scanning for the literal token
</event>and cutting right after it, the next declaration starting on the very next byte.
The streaming form in practice looks like this (abbreviated):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<event version="2.0" uid="RAVEN-1" type="a-f-G-U-C" how="m-g" …>…</event><?xml version="1.0" …?><event …>…</event>
That token-scanning splitter is protocol behavior, not an implementation quirk — TAK Server's own streaming codec documents the absence of delimiters and searches for the closing tag. Any gateway must emit events that split cleanly: complete events, declaration first, nothing in between. For annotated full events, see our CoT message examples.
TAK protocol version 1: the protobuf TakMessage
Version 1 replaces the XML text with a single proto3 message — atakmap.commoncommo.protobuf.v1.TakMessage — whose definition ships with the TAK distributions and is mirrored in libraries such as the takproto Python package. The structure is compact but strictly specified:
TakMessageholds two optional parts: aTakControl(protocol bookkeeping — min/max versions the sender decodes, plus a contact UID) and theCotEventitself.CotEventcarries the envelope:type,uid,how, optionalaccess/qos/opex, the three timestamps as milliseconds since the Unix epoch (sendTime,startTime,staleTime), and the point fieldslat,lon,hae,ce,leas doubles. Unknown height or error values use the sentinel999999, as in the XML convention.Detailis where it gets interesting. Six well-known detail elements became typed sub-messages:contact(endpoint, callsign),group(from__group: name, role),precisionLocation(geopointsrc, altsrc),status(battery),takv(device, platform, os, version),track(speed, course). Everything else — remarks, chat bodies, plugin elements — is stuffed into onexmlDetailstring holding the raw XML of the remaining children.
Two specification rules save teams from subtle corruption. First, whole elements only: a detail element is converted entirely into its typed message or left entirely in xmlDetail — never split, and if it cannot map cleanly it stays in xmlDetail. Second, receivers merge typed messages back into XML, and on conflict xmlDetail wins. New fields may only append at the end of existing messages; anything semantic needs a version bump. Practically, protobuf savings shrink as untyped detail grows — exactly what you see with chat- or plugin-heavy traffic.
Mesh and stream framing: the actual bytes
Both versions ride both transports; framing differs by transport, not by payload:
- Mesh datagram:
0xbf, a varint protocol version,0xbf, then the payload. The magic byte0xbf(191 decimal) lets a receiver sniff one datagram and know whether it is protobuf or legacy XML, which starts with<— TAK Server's UDP inputs make exactly that check on every datagram. - Streaming frame:
0xbf, a varint payload length in bytes, then the payload. No version byte — the version was negotiated once per connection (next section), so repeating it per frame would be waste.
The varints are standard unsigned protobuf varints — 7 bits at a time, least-significant first, high bit marking continuation, 64-bit maximum (10 bytes). A 258-byte payload encodes as 82 02; 127 is 7f, 128 is 80 01.
Because each frame carries its own length, a stream parser is a tiny state machine: expect 0xbf, accumulate the varint, buffer exactly that many bytes, decode, repeat — carrying partial frames across reads. Version 0 gives you none of that; you scan text for </event> and hope nothing truncates mid-event.
Streaming negotiation: t-x-takp-v, t-x-takp-q, t-x-takp-r
A client cannot know whether a server speaks protobuf, so the switch is negotiated inside the connection with three CoT control events — themselves sent as XML:
- The offer (
t-x-takp-v). A server that supports the TAK protocol may send one event of this type carrying<detail><TakControl><TakProtocolSupport version="1"/>— one element per supported version, at most once per connection, after authentication when the input requires it. - The request (
t-x-takp-q). The client picks a version from the offer and replies with<TakRequest version="1"/>, then must stop sending XML (while still processing inbound XML) and wait — at least a minute before giving up and reconnecting. - The response (
t-x-takp-r). The server answers<TakResponse status="true"/>or"false". Ontrue, both sides switch the whole connection to length-framed version 1 payloads — the same version both directions — and no more XML flows. Onfalse, the connection stays XML and the client may retry later.
All three events share one transaction UID (the specification's protouid), and the dance is strict: protobuf sent before the true response breaks against the reference server. On the mesh there is no handshake: every device broadcasts a TakControl message at least every 60 seconds declaring the minimum and maximum versions it can decode; contacts silent for two minutes revert to the version of their last decodable message; and everyone transmits at the highest version all known contacts can decode, falling back to XML whenever a legacy device is present. Load-test negotiation before assuming a fleet has switched — the tuning levers are in our TAK Server performance guide.
TAK Server ports: the default map
Defaults from the TAK Server 5.7 configuration guide and its example CoreConfig.xml; every one is configurable, so treat this as what a stock deployment opens, not a contract. The guide's own firewall guidance is minimal: open 8089 and 8443.
| Port | Protocol | Role |
|---|---|---|
8089 | TCP/TLS | CoT streaming input — what ATAK, WinTAK and gateways connect to with client certificates |
8090 | UDP (QUIC) | Optional QUIC streaming input, present in the current example config |
8087 | TCP or UDP | Unencrypted CoT input (example inputs; lab use only) |
8088 | TCP | Streaming TCP without TLS (stcp) — testing only |
8443 | TCP/HTTPS | Admin UI, REST API and WebTAK (client-certificate authenticated) |
8444 | TCP/HTTPS | Federation-facing HTTPS (federation truststore; mission package fetch) |
8446 | TCP/HTTPS | Certificate enrollment (CSR, HTTP Basic auth) and OAuth2 token endpoint |
9000 | TCP/TLS | Federation v1 listener |
9001 | TCP/TLS | Federation v2 listener (gRPC over HTTP/2) |
9002 | TCP/TLS | Federation token authentication (if enabled) |
239.2.3.1:6969 | UDP multicast | Mesh SA group (client default; bridgeable as a server input) |
Two footnotes. FreeTAKServer, the Python server of the open-source ecosystem (see our open-source TAK ecosystem guide), keeps 8089 for TLS CoT but uses 8087 for plain TCP CoT. And federation deserves its own read: the v1 link exchanges protobuf FederatedEvent messages behind a four-byte length prefix, while v2 runs a gRPC service with streaming RPCs and health checks — covered in our federation setup guide and Federation Hub architecture overview.
Message size and bandwidth: XML vs protobuf
We encoded two representative events both ways — an ATAK-style self-position report (typed detail plus an untyped uid element) and a small MAVLink-bridge drone track, with the reference Python encoder for the protobuf side:
| Event | CoT XML | XML on a stream (declaration included) | Protobuf payload | On the wire (framed) |
|---|---|---|---|---|
| ATAK self-position report | 578 B | 634 B | 258 B | 261 B |
| UAV bridge track | 363 B | 419 B | 171 B | 174 B |
A 52–59% reduction for these shapes — consistent with the 55–65% we cited in our drone telemetry integration article and with Isode's 2025 field measurements for TAK over HF radio: roughly 530-byte XML events versus 376–380-byte federated protobuf messages, before compression. Protobuf helps most where XML is pure envelope — declarations, attribute names, closing tags — while detail parked in xmlDetail keeps its XML size.
Why it matters on a radio: forty users self-reporting every ten seconds cost about 20 kbps in streamed XML (634 B each) versus about 8 kbps in protobuf, before per-message TLS record overhead. On narrowband the effect is brutal — at 75 bps, Isode measured 29–33 seconds of end-to-end latency for a single event. One more size behavior: TAK Server's streaming output refuses to send a protobuf TakMessage larger than 65,536 bytes; an oversized event is replaced by a small file-transfer pointer telling the client to fetch the full XML over HTTPS. Chat traffic is examined in our tactical chat data strategy article.
Sending CoT from Python: TLS, certificates, protobuf
Everything above collapses into a small program — the standard library alone can open a TLS connection to the 8089 input with a client certificate and stream version 0 XML events, no dependencies:
import asyncio
import datetime as dt
import ssl
import xml.etree.ElementTree as ET
TAK_HOST, TAK_PORT = "tak.example.org", 8089 # TLS streaming input
CA_FILE, CERT_FILE, KEY_FILE = "takserver-ca.pem", "client.pem", "client.key"
def ts(t): # CoT time: ISO 8601, UTC, millisecond precision
return t.strftime("%Y-%m-%dT%H:%M:%S.") + f"{t.microsecond // 1000:03d}Z"
def cot_event(uid, cot_type, lat, lon, hae, callsign, stale_s=60):
now = dt.datetime.now(dt.timezone.utc)
ev = ET.Element("event", version="2.0", uid=uid, type=cot_type, how="m-g",
time=ts(now), start=ts(now),
stale=ts(now + dt.timedelta(seconds=stale_s)))
ET.SubElement(ev, "point", lat=f"{lat:.7f}", lon=f"{lon:.7f}",
hae=f"{hae:.1f}", ce="10.0", le="9999999.0")
ET.SubElement(ET.SubElement(ev, "detail"), "contact", callsign=callsign)
return ET.tostring(ev, encoding="utf-8", xml_declaration=False)
async def drain(reader):
# Keep reading the server (its t-x-takp-v offer, other users' SA),
# or TCP flow control eventually stalls its writes to you.
while await reader.read(65536):
pass
async def main():
ctx = ssl.create_default_context(cafile=CA_FILE) # verify server cert + name
ctx.load_cert_chain(CERT_FILE, KEY_FILE) # our client identity
reader, writer = await asyncio.open_connection(TAK_HOST, TAK_PORT, ssl=ctx)
rx = asyncio.create_task(drain(reader))
try:
while not rx.done():
writer.write(cot_event("gw-demo.uav-12", "a-f-A-M-F-Q",
50.4501234, 30.5234567, 640.0, "UAV-12"))
await writer.drain()
await asyncio.sleep(5) # report well inside stale
finally:
writer.close()
asyncio.run(main())
Three details are load-bearing. Asyncio sockets disable Nagle's algorithm by default, so each event leaves immediately — with raw sockets, set TCP_NODELAY yourself. The drain task is not decoration: a server writing to a client that never reads eventually stalls on TCP flow control. Certificates must be PEM: TAK Server issues a PKCS#12 .p12 (what ATAK consumes), which OpenSSL converts in three lines:
openssl pkcs12 -in gateway-01.p12 -clcerts -nokeys -out client.pem -passin pass:SECRET
openssl pkcs12 -in gateway-01.p12 -nocerts -nodes -out client.key -passin pass:SECRET
openssl pkcs12 -in truststore-root.p12 -nokeys -out takserver-ca.pem -passin pass:SECRET
For protobuf, the takproto package (PyPI) converts CoT XML to and from version 1 payloads and adds the framing; the PyTAK library wraps the whole client — COT_URL accepts tls://host:8089 or udp+wo://239.2.3.1:6969, TAK_PROTO=1 selects protobuf, and certificates import straight from a TAK data package .zip. Build-it-yourself versus adopt-a-library is the calculus of our TAK C2 integration patterns article.
This is where a weekend prototype meets operational reality: certificate enrollment at fleet scale, protobuf negotiation across mixed client versions, multicast that must actually route. We build CoT/TAK protocol gateways, migrate feeds from XML to protobuf, and integrate and harden TAK Server. Tell us what you need to connect →
Delivery semantics, security, and the pitfalls that bite
No acknowledgements — stale times do the work
CoT is fire-and-forget at every layer: no per-message acks, no sequence numbers, no sessions. Freshness is managed by the stale timestamp — consumers age or drop events past it — and by cadence: send well inside your stale window or accept ghosts on the map. TAK Server softens reconnects with its latest SA buffer (new clients are immediately sent the last known positions of reachable users), and ATAK keeps connections alive with a ping (t-x-c-t) answered by a server pong (t-x-c-t-r). Re-send your current picture after any reconnect rather than assuming the server remembered it.
Certificates, groups, channels
Operational TAK traffic is mutual TLS: the client verifies the server against the deployment CA, the server verifies a client certificate from that same CA, and the certificate's CN becomes the username for group assignment. Group membership — per input, per certificate, or via LDAP/file backends — is the whole access-control model for who sees whose tracks; unmatched connections land in __ANON__. Federation deliberately uses a separate truststore, so a partner's CA never silently authorizes client connections, and enrollment runs on port 8446 behind HTTP Basic auth. Where a deployment needs HTTP APIs instead of raw sockets, that surface is described in our CloudTAK API integration guide.
The recurring failure modes
- Multicast that does not arrive: multicast forwards only while TTL allows (default TTL 1 keeps datagrams on the local segment; TAK Server's example config raises it to 5), IGMP snooping misconfiguration silently drops groups, and multicast never crosses NAT. Mesh SA is same-subnet technology.
- Clock skew: with stale-based aging, a wrong clock either evaporates everyone's tracks or lingers forever. Synchronize time (GNSS or NTP) before debugging anything else.
- Certificate format friction: the server speaks JKS, clients PKCS#12, your service PEM — and certificates missing the client-auth extended key usage are rejected at handshake. Convert deliberately and test the exact chain.
- Nagle's algorithm: a 200 ms batching delay is invisible on a datacenter link, lethal on a tactical one. Disable it or batch explicitly.
- Reconnect storms: every gateway reconnecting at once after a radio outage looks like a DDoS. Use jittered exponential backoff, re-authenticate, replay current state.
- Mixed-version assumptions: one legacy client on the mesh drops everyone back to XML, and a server that never offers
t-x-takp-vnever gets protobuf. Instrument which encoding your links settled on — bytes on the wire per minute tells the truth.
Reference architecture: a CoT gateway service
The durable pattern for feeding a TAK network from non-TAK systems is a small, dedicated gateway service, not code embedded in every source:
- Inbound adapters — MAVLink, radar, AIS, GPS trackers, REST/WebSocket feeds — each normalized to an internal track model.
- A CoT builder — one place owning UID policy (stable, namespaced, collision-resistant), type mapping,
howcodes and per-source stale windows, emitting XML or protobuf through the same detail rules. - An egress supervisor — a pool of authenticated TLS connections with negotiation (protobuf when offered, XML otherwise), jittered reconnect, per-link rate shaping for narrowband paths, and current-state replay.
- Operations surface — link health, encoding in use, bytes per minute and certificate-expiry monitoring; on tactical networks the protocol is the first thing blamed and the last thing instrumented.
That shape — a protocol-faithful edge between someone else's data and a TAK Server — is a component we build for defense programs, alongside the TAKpilot copilot on the same infrastructure. If your program is drawing this diagram now, bring in a team that has shipped it before.
Need this on your network?
We build CoT/TAK protocol gateways, migrate feeds from XML to protobuf, and integrate and harden TAK Server — certificates, groups and federation included.
Prepared by the Corvus Intelligence engineering team, which builds CoT/TAK gateways, ATAK plugins and TAK Server integrations for defense programs (ISO 9001/27001 certified, Brave1 member). About Corvus Intelligence →