---
title: Broadcast with gossip
description: Join a topic, broadcast to the swarm, and read events — the iroh-gossip pub/sub mesh in Go.
icon: share-nodes
---

Gossip gives you topic-based broadcast across a mesh of endpoints without a
server. Peers join a topic, maintain a partial view of neighbors (HyParView),
and relay messages to the swarm (PlumTree), so a message reaches every
subscriber without anyone holding a connection to everyone.

The Go package is `github.com/tmc/go-iroh/gossip`, a port of the Rust
`iroh-gossip` crate speaking `/iroh-gossip/1`. Upstream's conceptual treatment
is [Gossip Broadcast](https://docs.iroh.computer/connecting/gossip).

## Wire it up

Gossip is a protocol behind a `Router`, like any other:

```go
ep, err := iroh.Bind(ctx)
if err != nil {
	return err
}
g := gossip.NewGossip(ep)
_, err = iroh.NewRouter(ep, map[string]iroh.ProtocolHandler{
	gossip.ALPN: g.Handler(),
}, nil)
```

`gossip.ALPN` is the registered protocol name — do not invent your own, or you
will not interoperate with Rust peers.

## Topics

A topic is a 32-byte ID. Derive it from a name with BLAKE3 so every peer that
knows the name computes the same ID:

```go
topic := gossip.TopicID(blake3.Sum256([]byte("example/chat")))
```

Subscribing needs at least one bootstrap peer to reach an existing swarm — the
mesh has to be entered somewhere:

```go
// First peer: nothing to bootstrap from yet.
ta, err := ga.Subscribe(ctx, topic, nil)

// Second peer: bootstrap from the first, and wait until actually joined.
tb, err := gb.SubscribeAndJoin(ctx, topic, []netaddr.EndpointAddr{seed})
```

`Subscribe` returns as soon as the subscription exists locally;
`SubscribeAndJoin` additionally waits for the swarm join to complete. Use the
latter when the next thing you do is broadcast — otherwise the message can go
out before you have a neighbor and reach nobody. `Topic.Joined` and
`Topic.IsJoined` let you check explicitly.

## Broadcast and receive

```go
if err := tb.Broadcast(ctx, []byte("hello topic")); err != nil {
	return err
}

for ev, err := range ta.Events() {
	if err != nil {
		return err
	}
	if ev.Kind == gossip.Received {
		fmt.Printf("received: %s (from %s)\n", ev.Content, ev.DeliveredFrom.Z32()[:8])
		break
	}
}
```

Running the two-endpoint version of this prints:

```text
B neighbors: 1
A received: hello topic (from bibzrh75)
```

`Events()` is an `iter.Seq2[Event, error]`, so it works with `range`. Event
kinds include `NeighborUp` and `NeighborDown` alongside `Received`, which is
how you track the mesh as it changes.

**`DeliveredFrom` is the last hop, not the original sender.** In a multi-hop
mesh the message may have been relayed, so do not use it as an author
identity. If you need to know who wrote a message, put the author in your own
payload and sign it.

`Broadcast` reaches the whole swarm; `BroadcastNeighbors` reaches only your
immediate neighbors.

## Sizing and splitting

`gossip.WithMaxMessageSize(n)` sets the cap at construction, readable back with
`Gossip.MaxMessageSize()`. Oversized messages fail to broadcast rather than
fragmenting, so keep payloads well under the limit and chunk in your own
protocol if you need more.

`Topic.Split()` returns a `*Sender` and `*Receiver` when you want to hand the
two halves to different goroutines.

## Discovery on top of gossip

The package also ships `gossip.Discovery`, which uses a gossip topic as a peer
discovery channel — `gossip.DefaultDiscoveryTopic` is the well-known ID. It
implements the same publisher/resolver interfaces as the other discovery
services, so it registers with `iroh.AddressLookupServices` like DNS or mDNS.
See [Relays and discovery](relays-and-discovery).

## Next steps

- [Protocol packages](protocols) — blobs and docs, the other two ports.
- [Endpoints and connections](endpoints-and-connections) — what `Router` is
  doing underneath.
