Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"iroh-services/quickstart",
"iroh-services/projects",
"iroh-services/access",
"iroh-services/tickets",
"iroh-services/support"
]
},
Expand Down
183 changes: 183 additions & 0 deletions iroh-services/tickets.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
---
title: "Hosted Tickets"
description: "Automatically exchange tickets between app instances using Iroh Services"
---

Tickets tell your application what to do. They bundle connection details and
application-specific data into a single token that one app instance can create
and others can consume. The tickets service gives you a central place to
publish and discover these tickets, so your users don't need to copy-paste
strings or scan QR codes to get connected.

<Note>
New to tickets? Read the [Tickets concept page](/concepts/tickets) first for
background on how tickets work in iroh.
</Note>

## The Problem

Iroh tickets are powerful — they contain everything needed to connect to a peer
and start working. But getting a ticket from one device to another has always
required an outside channel: a text message, a QR code, a shared clipboard.

For many applications, that manual handoff is friction you'd rather remove. If
your users are already signed into a service, why should they need to text
themselves a ticket?

## How It Works

The tickets service is a central server where your application can **publish**
and **fetch** tickets scoped to your [project](/iroh-services/projects). Any
endpoint connected to your project can list and retrieve tickets published by
other endpoints in the same project.

The workflow is simple:

1. **Alice publishes a ticket** — her app creates a ticket and publishes it to
the service with a name
2. **Bob lists or fetches tickets** — his app queries the service and gets
Alice's ticket back
3. **Bob connects to Alice** — using the ticket's connection details, Bob's app
connects directly to Alice

Tickets are **live** — they're only available while the publishing endpoint is
online. When Alice's endpoint disconnects, her tickets are automatically removed.

## Defining a Ticket Type

Tickets use the exact same format you'd use for a QR code or copy-paste. The
key principle is: **tickets tell your application what to do, and there are many
ways to get tickets between app instances**. The tickets service is one of those
ways, but the ticket format itself is transport-agnostic.

Your application should define a custom ticket type that carries
whatever data your app needs:

```rust
use iroh::EndpointId;
use iroh_tickets::Ticket;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
struct TopicTicket {
message: String,
endpoint_id: EndpointId,
}

impl Ticket for TopicTicket {
const KIND: &'static str = "coolapp";

fn to_bytes(&self) -> Vec<u8> {
postcard::to_stdvec(&self).expect("serialization failed")
}

fn from_bytes(bytes: &[u8]) -> Result<Self, iroh_tickets::ParseError> {
Ok(postcard::from_bytes(bytes)?)
}
}
```

The `KIND` constant is a short string prefix that identifies your ticket type.
This is what you'd see at the start of a ticket string — the same string whether
the ticket comes from a QR code, a URL, or the tickets service.

Design your ticket type to carry the information your app needs to act on it.
Include endpoint IDs for connection details, content hashes for data references,
topic names, capabilities — whatever makes sense for your use case.

## Publishing Tickets

To publish a ticket, create your n0des client and call `publish_ticket` with a
name and your ticket value:

```rust
use iroh::Endpoint;
use iroh_n0des::Client;

let endpoint = Endpoint::bind().await?;
let client = Client::builder(&endpoint)
.api_secret_from_env()?
.build()
.await?;

let ticket = TopicTicket {
message: "cool_pokemon".to_string(),
endpoint_id: endpoint.id(),
};

client.publish_ticket("alice_cool_pokemon", ticket).await?;
```

Ticket names are strings scoped to your project. Choose a naming convention
that works for your application — for example, prefixing with a username or
device identifier.

## Fetching & Listing Tickets

Other endpoints in your project can fetch a specific ticket by name, or list
all available tickets:

```rust
// Fetch a specific ticket by name
let ticket = client
.fetch_ticket::<TopicTicket>("alice_cool_pokemon")
.await?;

if let Some(published) = ticket {
println!("found ticket: {:?}", published.ticket);
println!("published by: {}", published.name);
}

// List all tickets in the project
let all_tickets = client
.fetch_tickets::<TopicTicket>(0, 100)
.await?;

for t in &all_tickets {
println!("{}: {:?}", t.name, t.ticket);
}
```

## Tickets Go Offline With You

Published tickets are live — they're available as long as the publishing endpoint
is connected. When an endpoint disconnects, its tickets are automatically
cleaned up. This keeps the ticket list fresh and avoids stale entries pointing
to peers that are no longer reachable.

```rust
// Alice publishes a ticket
alice_client.publish_ticket("my_topic", ticket).await?;

// Bob can see it
let tickets = bob_client.fetch_tickets::<TopicTicket>(0, 100).await?;
assert_eq!(tickets.len(), 1);

// Alice goes offline
alice_endpoint.close().await;

// After a short delay, the ticket is cleaned up
let tickets = bob_client.fetch_tickets::<TopicTicket>(0, 100).await?;
assert_eq!(tickets.len(), 0);
```

## One Format, Many Channels

The ticket format is the same regardless of how it's delivered. A `TopicTicket`
published through the tickets service serializes to the same string as one
shared via a QR code or pasted into a chat. This means your application can
support multiple discovery methods without changing its ticket handling:

- **Tickets service** for automatic discovery between signed-in users
- **QR codes** for in-person device pairing
- **Deep links** for sharing via messaging apps
- **Copy-paste** for developer workflows and debugging

Your app only needs to know how to *handle* a ticket — how the ticket arrives
is a separate concern.

## Next Steps

- [Tickets concept page](/concepts/tickets): How tickets work under the hood
- [Projects](/iroh-services/projects): Understanding project scoping
- [Quickstart](/iroh-services/quickstart): Set up your first Iroh Services app
Loading