Quickstart

First 60 seconds with Repull. Six steps from zero to a live integration with webhooks.

TL;DR

One Bearer token. https://api.repull.dev. Cursor pagination. Idempotency-Key on writes. Steps 1 and 2 need only your API key; connecting a channel comes in step 3.

1

Get an API key

Mint a key from /dashboard/keys. There is one key type — sk_live_… — and it acts on whatever channels your workspace has connected. Store the key in your secrets manager and expose it as an env var.

# .env
REPULL_API_KEY=sk_live_paste_your_key_here

Done? Next: Make your first call

2

Make your first call

List the workspace's listings — your first authenticated call, no channel connection required yet.

curl "https://api.repull.dev/v1/listings?limit=10" \
  -H "Authorization: Bearer $REPULL_API_KEY"

Returns { data, pagination }. Cursor pagination — pass pagination.nextCursor back as cursor=… on the next call to walk the rest.

Done? Next: Connect a channel

3

Connect a channel

For real data, the workspace needs at least one channel connection. Mint a Connect session and redirect the user — the picker UI walks them through the OAuth or credentials flow for whichever channel they choose. Full guide: Connect (multi-channel).

curl -X POST https://api.repull.dev/v1/connect \
  -H "Authorization: Bearer $REPULL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"redirectUrl": "https://your-app.com/callback"}'

Done? Next: Loop reservations

4

Loop reservations

With a connection in place, list reservations across every connected channel through a single endpoint. Filter by status, date range, listing — the API is provider-agnostic.

curl "https://api.repull.dev/v1/reservations?limit=50" \
  -H "Authorization: Bearer $REPULL_API_KEY"

limit caps at 100. Use the cursor pattern from Step 2 to walk the rest. See Get Reservations for the full filter list.

Done? Next: Register a webhook

5

Register a webhook for new bookings

Polling the list endpoint works — but webhooks let you react in seconds. Register an endpoint and pick the events you care about. Repull retries with exponential backoff for 24 hours on any non-2xx response. Full guide: Webhooks.

curl -X POST https://api.repull.dev/v1/webhooks \
  -H "Authorization: Bearer $REPULL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/repull",
    "events": ["reservation.created", "reservation.updated", "reservation.cancelled"]
  }'

The response includes secret — store it. You will need it in Step 6 to verify deliveries.

Done? Next: Verify webhook signatures

6

Verify webhook signatures

Every webhook delivery includes an X-Repull-Signature header (t=…,v1=…). Verify it before processing — an unverified payload could come from anywhere. Full guide: Verify webhook signatures.

import crypto from 'node:crypto'

export async function POST(req: Request) {
  const raw = await req.text() // the raw body — never re-serialised JSON
  const header = req.headers.get('X-Repull-Signature') ?? '' // "t=…,v1=…"
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')))
  const age = Math.floor(Date.now() / 1000) - Number(parts.t)
  if (!parts.t || !parts.v1 || !(Math.abs(age) <= 300)) {
    return new Response('stale or malformed signature', { status: 401 })
  }

  const expected = crypto
    .createHmac('sha256', process.env.REPULL_WEBHOOK_SECRET!)
    .update(`${parts.t}.${raw}`)
    .digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(parts.v1)
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return new Response('invalid signature', { status: 401 })
  }

  const event = JSON.parse(raw) // event.type, event.data
  return new Response('ok')
}

Verification recomputes HMAC-SHA256 over `${t}.${raw_body}` and compares it to v1. Use the raw body — re-serialising parsed JSON breaks the hash.

What next

AI