← Back to blog
Product July 17, 2026 by Javier Arancibia

Your agent can call any API. It can't be called back. So I built the other half.

Your agent can call any API. It can't be called back. So I built the other half.

An AI agent calling an API is a solved problem — that's the easy half of the agent web. The hard half is the reverse: being called back. The moment an agent needs to receive something — an OAuth redirect after a login, a "your deploy finished" webhook, a Stripe payment confirmation, a human clicking approve — it hits a wall. A process running inside a terminal, a CI job, or a Claude Code session has no public URL. There is nowhere for the callback to land.

So I built relais: the missing inbound address for a process that has none. It gives your agent a URL that catches any HTTP request, and — the part I like most — a long-poll the agent can curl and block on until the thing arrives. No poll loop, no infrastructure. Three curls.

Watch it catch a real GitHub webhook

This is the exact flow every agent integration eventually hits, run end to end against production. First, make an inbox:

I=$(curl -s -X POST https://relais.intrane.fr/v1/inboxes -d '{"label":"gh"}')
CATCH=$(echo "$I" | jq -r .catch_url); TOK=$(echo "$I" | jq -r .token)

Now the agent blocks on the inbox — this call parks the connection open until something lands, instead of hammering a poll loop:

curl -s "https://relais.intrane.fr/v1/wait?timeout_ms=45000" \
  -H "Authorization: Bearer $TOK" &

Then point a real GitHub webhook at the catch URL. GitHub fires a ping event the instant you create one:

gh api repos/<you>/<repo>/hooks -X POST -f name=web -F active=true \
  -f 'events[]=push' -f "config[url]=$CATCH" -f 'config[content_type]=json'

The blocked /v1/wait returns the moment the ping arrives — with the real payload:

{"waiting":false,"message":{"method":"POST",
  "headers":{"x-github-event":"ping"},
  "body":"{\"zen\":\"Half measures are as bad as nothing at all.\",\"hook_id\":653798437,…}"}}

A real third party (GitHub) delivered a real webhook to an agent that otherwise had no way to receive one — caught, blocked-on, and read. Swap GitHub for an OAuth provider, a CI pipeline, or Stripe and it's the same three curls. Secrets like Authorization and Cookie are never stored; the body is capped.

Free is ephemeral, persistent is paid

A free inbox expires in an hour and holds 100 messages — perfect for a one-shot OAuth dance. But if you need a stable URL to register with Stripe or GitHub or an OAuth app, you want it to stick around. So a persistent inbox (30-day TTL, thousands of messages) is bought with a peage wallet — one header, no subscription:

curl -s -X POST https://relais.intrane.fr/v1/inboxes \
  -H 'X-Peage-Wallet: pw_…' -d '{"label":"stripe prod"}'

This is the pattern I keep coming back to. péage is the pay half of the agent web — a fiat rail where an agent holds a prepaid wallet its human funds once, and any API charges it per call. relais is the receive half. An agent funds a wallet on péage, then spends it on relais (or grepapi, or hart). Two French infrastructure words for the two things an autonomous process can't do for itself: pay, and be reached.

Written for agents, not people

There's no dashboard. The docs are at relais.intrane.fr/llms.txt, written for the machine that will actually use them. The whole service is a single ~110 KB static binary in machin, my machine-first language — no Node, no runtime, no container. It runs, it catches, it waits.

If your agents need to be reachable — and eventually they all do — point a webhook at relais.intrane.fr and block on it. Source and the full walkthrough: github.com/javimosch/relais. I answer email: javi@intrane.fr.