Skip to main content

Inbound Endpoints

Receive webhooks from external services, store every payload, and forward them to your app with retries and serialized delivery.

How It Works

Create an endpoint and you get a unique URL. Point external services (Stripe, GitHub, etc.) at that URL. When a webhook arrives, Runlater stores the raw payload and forwards it to your app — with automatic retries if your server is down.

  1. Create an endpoint with a name and forward URLs
  2. Copy the inbound URL and paste it into Stripe, GitHub, or any service
  3. Runlater receives the webhook, stores the full payload, and forwards it to your app
  4. If forwarding fails, we retry with exponential backoff (configurable, 0-10 attempts)
  5. Choose serial (queued) or parallel delivery per endpoint
Why use this? External services don't retry forever. If your server is down when Stripe sends a webhook, you lose it. Runlater acts as a buffer — we always accept the webhook and forward it when your server is ready.

Quick Start

1. Create an endpoint

curl -X POST https://runlater.eu/api/v1/endpoints \
  -H "Authorization: Bearer pk_xxx.sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe webhooks",
    "forward_urls": ["https://myapp.com/webhooks/stripe"],
    "retry_attempts": 5,
    "use_lane": true
  }'

The response includes an inbound_url — this is the URL you give to Stripe (or any external service).

2. Configure the external service

Copy the inbound URL and paste it into the webhook settings of the external service. The URL looks like:

https://runlater.eu/in/ep_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

3. Webhooks start flowing

Every request to the inbound URL is stored and forwarded to your forward URLs. The original HTTP method, headers, and body are preserved.

Delivery Modes

Serial delivery (queue enabled — default)

When use_lane is enabled (the default), events are forwarded one at a time — only one event per endpoint runs at any moment. This prevents race conditions for services like Stripe, where overlapping requests could cause double-billing.

Under the hood, each endpoint uses a lane based on the endpoint name to guarantee serialized execution.

Note: If an event fails, it will be retried with exponential backoff. Other events in the queue will continue to be delivered while the failed event waits to be retried.

Parallel delivery (queue disabled)

Set use_lane to false when event ordering doesn't matter. Events are forwarded concurrently for faster throughput. Ideal for stateless webhooks like analytics events or notifications.

Retry Configuration

Each endpoint has a configurable number of retry attempts (0-10, default: 5). When forwarding fails, Runlater retries with exponential backoff. Set to 0 to disable retries entirely.

Connection Pausing

Pause webhook delivery when your target server is down for maintenance or a deploy. Events are still received and logged, but executions are held until you resume.

  • Paused — events accepted (HTTP 200), executions queued but not forwarded
  • Resumed — workers wake up and process the backlog automatically
  • Disabled — events rejected (HTTP 503). Different from pausing.
Pause vs Disable: Disabling an endpoint rejects all incoming webhooks with HTTP 503. Pausing still accepts and stores them — they just won’t be forwarded until you resume.

Via API

# Pause delivery
curl -X POST https://runlater.eu/api/v1/endpoints/:id/pause \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

# Resume delivery
curl -X POST https://runlater.eu/api/v1/endpoints/:id/resume \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

Deduplication

Many webhook providers (Stripe, GitHub, Shopify) deliver events at least once — meaning your endpoint may receive the same event multiple times. Use the Lua transform() function to deduplicate by returning a debounce_key and debounce window. Within the window, only one forwarding execution is created per key.

-- Deduplicate Stripe events by event ID
function transform(event)
  local data = json_decode(event.body)
  return {
    debounce_key = data.id,
    debounce = "30s"
  }
end

The first event with a given key schedules an execution at now + debounce. If the same key arrives again within the window, it replaces the pending execution but keeps the original scheduled time — so the latest payload is always forwarded, but only once.

Common dedup keys: Stripe: data.id — GitHub: event.headers["x-github-delivery"] — Shopify: event.headers["x-shopify-webhook-id"]

Debounce requires a Lua script (Pro only). See the Lua debounce reference for the full details.

Custom Forwarding

Override the HTTP method, headers, or body for all forwarded requests — no code required. Set these on any endpoint to apply static defaults to every event before it’s forwarded.

Field Effect
forward_method Replaces the original HTTP method (e.g. always forward as POST)
forward_headers Merged with the original headers — custom headers win on conflict
forward_body Replaces the original request body entirely

Leave a field empty to forward the original value. Custom Forwarding is available on all tiers.

Use case: A third-party service sends GET webhooks but your app only accepts POST. Set forward_method to POST and every event is forwarded as a POST request — no Lua needed.

Processing Order

When an event arrives, Lua verification and filtering run on the original request. Custom Forwarding and Lua transform() only affect what gets forwarded:

receive → verify(original)filter(original)custom forwardingtransform()route() → forward
  1. verify() and filter() always see the original headers, body, and method — so signature checks work correctly.
  2. Custom Forwarding applies static defaults (method, headers, body) for the forwarded request.
  3. Lua transform() runs last. Any values it returns override the Custom Forwarding values. Fields it doesn’t return keep the Custom Forwarding value.
Free vs Pro: Free tier users can use Custom Forwarding (no Lua). Pro tier users get both — Lua transform() takes priority when it returns values.

Lua Scripting Pro

Write Lua scripts to verify signatures, filter unwanted events, transform payloads, and route to specific URLs. See the full Lua Scripting documentation for the complete reference, examples, and error handling details.

Event Replay

Every inbound event is stored with the full request payload. You can replay any event from the dashboard or via the API — useful for reprocessing after a bug fix.

curl -X POST https://runlater.eu/api/v1/endpoints/:id/events/:event_id/replay \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

Inbound URL

The inbound URL accepts any HTTP method (GET, POST, PUT, PATCH, DELETE). No authentication is needed — the slug in the URL is the authentication, just like monitor ping tokens.

Responses

Status Body Meaning
200 {"id": "...", "status": "received"} Event stored and queued for forwarding
200 {"id": "...", "status": "queued"} Event stored but delivery is paused — will be forwarded on resume
403 {"id": "...", "status": "rejected"} Event rejected by Lua script (default for verify; script can override status code)
404 {"error": "Not found"} Invalid slug
503 {"error": "Endpoint disabled"} Endpoint is disabled (providers will retry)

Endpoints API

Manage endpoints programmatically via the REST API. All endpoints require an API key passed as a Bearer token. See the API Reference for authentication details.

List Endpoints

curl https://runlater.eu/api/v1/endpoints \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

Create Endpoint

Field Type Description
name string Display name (required)
forward_urls array List of URLs to forward events to (required, at least one)
retry_attempts integer Number of retry attempts, 0-10 (default: 5)
use_lane boolean Enable serialized delivery (one at a time). Set to false for parallel. (default: true)
enabled boolean Whether the endpoint accepts events (default: true)
forward_method string Override HTTP method for forwarded requests (e.g. "POST")
forward_headers object Headers merged with originals (custom headers override on conflict)
forward_body string Replace original body for forwarded requests
script string Lua script for webhook processing (Pro only)
secrets object Encrypted key-value pairs accessible in verify() (Pro only, write-only)
curl -X POST https://runlater.eu/api/v1/endpoints \
  -H "Authorization: Bearer pk_xxx.sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "GitHub webhooks",
    "forward_urls": ["https://myapp.com/webhooks/github"],
    "retry_attempts": 3,
    "use_lane": false
  }'

Get Endpoint

curl https://runlater.eu/api/v1/endpoints/:id \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

Update Endpoint

curl -X PUT https://runlater.eu/api/v1/endpoints/:id \
  -H "Authorization: Bearer pk_xxx.sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated Name",
    "forward_urls": ["https://myapp.com/webhooks/new-url"]
  }'

Delete Endpoint

curl -X DELETE https://runlater.eu/api/v1/endpoints/:id \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

List Events

curl https://runlater.eu/api/v1/endpoints/:id/events \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

Replay Event

curl -X POST https://runlater.eu/api/v1/endpoints/:id/events/:event_id/replay \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

Pause Endpoint

curl -X POST https://runlater.eu/api/v1/endpoints/:id/pause \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

Resume Endpoint

curl -X POST https://runlater.eu/api/v1/endpoints/:id/resume \
  -H "Authorization: Bearer pk_xxx.sk_xxx"

For the full API specification including request/response schemas, visit the interactive API docs.

Use Cases

Stripe webhooks

Never miss a payment event. Runlater buffers Stripe webhooks and forwards them to your app, even during deployments or downtime.

GitHub / GitLab events

Receive push, PR, and issue events reliably. Replay events to reprocess after a bug fix.

Third-party integrations

Any service that sends webhooks — Shopify, Twilio, SendGrid, Slack — can be received and forwarded with full history and retry support.

Tier Limits

Free Pro
Endpoints 3 Unlimited
Event history 30 days 30 days
Lua scripting Verify, filter, transform, route