# Pier: Stellar events, delivered

> Subscribe to Stellar contract events and payments once. Pier watches every ledger and delivers each match to webhooks, live streams, the pull API, message queues, chat apps, your laptop or an AI agent, with retries, replay and a dashboard. No poller to write. Testnet and mainnet.

This is the Markdown version of [the Pier home page](https://pier-delta.vercel.app/). For the API reference written for agents, read [llms.txt](https://pier-delta.vercel.app/llms.txt).

Demo: [an exchange deposit](https://pier-delta.vercel.app/deposit), a real testnet payment with a memo that Pier delivers to RabbitMQ and the exchange credits.

## Quickstart

Get a key on the [dashboard](https://136-112-187-130.sslip.io/dashboard/) (GitHub sign-in), then pick your tool. Needs Node.js 20.19 or newer.

### CLI

```sh
# 1. log in with the key from the dashboard
npx pier-cli login --key pier_… --api https://136-112-187-130.sslip.io
# 2. subscribe: XLM transfers on testnet (add --network mainnet for mainnet)
npx pier-cli subscribe --contract CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC --topic sym:transfer,**
# 3. forward every match to a local server, resuming where you stopped
npx pier-cli listen <subscription-id> --forward http://localhost:3000/webhook
```

### TypeScript

```ts
// npm install @wmendes/pier-sdk
import { Pier, filters } from "@wmendes/pier-sdk";

const pier = new Pier({ apiKey: process.env.PIER_API_KEY!, apiUrl: "https://136-112-187-130.sslip.io" });
const sub = await pier.subscriptions.create({
  filter: filters.payments("G…your account", "in"),
});

for await (const event of pier.stream(sub.id)) {
  console.log(event.position, event.decoded.topic, event.decoded.value);
}
```

### HTTP

```sh
# a subscription that also posts each match to your webhook (signed, retried)
curl -X POST https://136-112-187-130.sslip.io/v1/subscriptions \
  -H "x-api-key: $PIER_API_KEY" -H "content-type: application/json" \
  -d '{"filter":{"type":"payment","account":"G…","direction":"in"},
       "webhook":{"url":"https://example.com/pier"}}'

# or pull: every match after a cursor
curl -H "x-api-key: $PIER_API_KEY" "https://136-112-187-130.sslip.io/v1/subscriptions/<id>/events?after=0"
```

### AI agent

```sh
# Claude Code: add the Pier MCP server, then ask for the events you need
claude mcp add pier -e PIER_API_KEY=pier_… -e PIER_API_URL=https://136-112-187-130.sslip.io -- npx -y @wmendes/pier-mcp

# any agent: point it at the docs written for agents
https://136-112-187-130.sslip.io/llms.txt
https://136-112-187-130.sslip.io/skill.md
```

## Destinations

Up to five destinations per subscription, added from the dashboard or the API. Credentials are stored encrypted and never shown again.

- **Webhooks:** signed with Standard Webhooks; each event retried on its own with backoff.
- **Server-Sent Events:** a live stream that resumes from the last event you handled.
- **Pull API:** every match after a cursor, page by page, for 7 days.
- **Your laptop:** `pier-cli listen --forward` posts to localhost. No public URL, no tunnel.
- **Apache Kafka:** keyed by subscription; TLS and SASL (PLAIN, SCRAM).
- **RabbitMQ:** persistent messages with publisher confirms.
- **NATS JetStream:** message ids let the stream drop a re-sent event.
- **Amazon SQS:** standard or FIFO; FIFO also drops a re-send within 5 minutes.
- **Google Pub/Sub:** attributes carry the position and event id.
- **Azure Service Bus:** queues and topics; the event id as message id drops a re-send.
- **Azure Event Hubs:** partitioned by subscription.
- **Azure Event Grid:** Event Grid schema or CloudEvents, on topics and namespaces.
- **Discord, Slack, Telegram:** one readable line per event in a channel.
- **AI agents:** an MCP server (`npx -y @wmendes/pier-mcp`), a [Pier skill](https://136-112-187-130.sslip.io/skill.md) and [llms.txt](https://pier-delta.vercel.app/llms.txt).
- **Dashboard:** filter builder with a preview, live events, delivery health and usage.

## Limits and guarantees

| | |
|---|---|
| Delivery | At least once. Pushed destinations don't preserve order: order by position and de-duplicate by event id. Pull and streams return events by position. |
| Retries | Exponential backoff from 5 seconds to 10 minutes, up to 200 attempts per event; up to 4 deliveries in flight per destination. |
| Retention | 7 days of events, for replay and pull. |
| Networks | Testnet; mainnet with filters that name contracts or an account, and 10,000 matched events per team per day. |
| Per team | 20 subscriptions, 5 destinations each, 10 open streams, 120 API requests a minute. |
| If Pier is down | Your cursor waits for you, and you can always read Stellar RPC [getEvents](https://developers.stellar.org/docs/data/apis/rpc/api-reference/methods/getEvents) directly. |

## Questions

- **What is a position?** Every event Pier stores gets a position: a number that only grows. It is the cursor for pull and streams, the SSE event id, and part of each webhook's id, so you can order and de-duplicate what you receive.
- **Do I need my own RPC node?** No. Pier reads Stellar RPC for you and matches every event against your filters; you only receive what matches.
- **What happens when my endpoint is down?** Each event is retried with backoff while other events keep flowing. After 200 failed attempts the destination stops; resume it from the dashboard or the API when you're ready.
- **Can I keep my API key in a browser app?** No. Keep it on a server. For browser apps, read the stream on your server and pass events on.

## Packages

- [pier-cli](https://www.npmjs.com/package/pier-cli): `npx pier-cli login --key … --api https://136-112-187-130.sslip.io`
- [@wmendes/pier-sdk](https://www.npmjs.com/package/@wmendes/pier-sdk): `npm i @wmendes/pier-sdk`
- [@wmendes/pier-mcp](https://www.npmjs.com/package/@wmendes/pier-mcp): `npx -y @wmendes/pier-mcp`

## Coming later

Backfill from the ledger archive, and Terraform.
