Forwarding Webhooks
Forwarding is what makes webhook.rodeo powerful—we receive webhooks, capture them for debugging, and then reliably forward them to your application with automatic retries and full delivery tracking.
How forwarding works
When you configure a forward URL on your webhook, every incoming event follows this flow:
- Event received - webhook.rodeo captures the incoming webhook
- Event logged - Complete request data saved to database
- Forwarding initiated - We send the webhook to your URL
- Response captured - Status, headers, body, and latency recorded
- Retries on failure - Automatic retries if forwarding fails
- Final status - Success or failure after all attempts
All of this happens asynchronously using durable workflows, so webhooks will be reliably delivered.
Headers preserved
When forwarding, we preserve all original headers from the sending service and add a few of our own:
POST https://your-app.com/api/webhooks
Content-Type: application/json
X-GitHub-Event: push
X-Rodeo-Request-Id: req_abc123
X-Rodeo-Webhook-Id: webhook_xyz789
X-Rodeo-Attempt: 1
See our Headers Reference for details on special headers we add.
Automatic retries
When forwarding fails (network error, 5xx response, timeout), webhook.rodeo automatically retries with exponential backoff:
| Attempt | Timing | Delay |
|---|---|---|
| 1 | Immediate | 0s |
| 2 | After failure | 30s |
| 3 | After failure | 2 minutes |
| 4 | After failure | 10 minutes |
What counts as a failure?
Forwarding is considered failed when:
-
Network errors - DNS failure, connection timeout, connection refused
-
5xx responses - Server errors (500, 502, 503, 504)
-
Timeouts - No response within 30 seconds
-
4xx status codes - 400, 404, 422, etc. (client errors don't retry)
4xx responses not retried because they indicate your server received and processed the request—it just didn't like what was sent.
What counts as success?
Forwarding succeeds when your endpoint returns:
- 2xx status codes - 200, 201, 202, 204, etc.
Retry behavior example
Let's say your endpoint is temporarily down:
10:00:00 - Attempt 1: Connection refused ❌
10:00:30 - Attempt 2: Connection refused ❌ (30s later)
10:02:30 - Attempt 3: Connection refused ❌ (2m later)
10:12:30 - Attempt 4: 200 OK ✅ (10m later)
Your endpoint came back online before the final retry, and the webhook was successfully delivered. All four attempts are logged for your review.
Delivery tracking
Every forward attempt creates a delivery record with complete details:
{
"id": "delivery_123",
"event_id": "evt_abc",
"attempt_number": 2,
"succeeded": true,
"request_headers": {
"content-type": "application/json",
"x-rodeo-request-id": "req_xyz",
"x-rodeo-attempt": "2"
},
"request_body": "{...}",
"response_status": 200,
"response_headers": {
"content-type": "application/json",
"x-request-id": "your-app-request-id"
},
"response_body": "{\"status\":\"processed\"}",
"response_latency_ms": 145,
"created_at": "2024-01-15T10:00:30Z"
}
Viewing delivery attempts
In the event detail page, the Delivery section shows:
- All attempts in chronological order
- Status badge (Success/Failed)
- Response status code
- Latency in milliseconds
- Expand to see full response headers and body
This gives you complete visibility into what happened when webhook.rodeo tried to forward to your endpoint.
Response inspection
webhook.rodeo captures up to 10KB of response body from your endpoint. This lets you see:
- Success messages
- Error details
- Validation errors
- Processing status
For example, if your endpoint returns:
{
"status": "processed",
"order_id": "ord_12345",
"message": "Webhook received and order updated"
}
You'll see this in the delivery details, confirming your endpoint processed the webhook correctly.
SSRF protection
Server-Side Request Forgery (SSRF) protection prevents malicious users from using webhook.rodeo to probe internal networks or attack other services.
Blocked destinations
webhook.rodeo blocks forwarding to:
- Localhost -
127.0.0.1,localhost,::1 - Private networks -
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 - Link-local -
169.254.0.0/16 - Reserved IPs -
0.0.0.0/8,240.0.0.0/4
If you try to use a blocked IP, you'll see:
{
"error": "Invalid forward URL",
"reason": "SSRF Protection: Private IP addresses are not allowed"
}
Allowed destinations
Any publicly routable HTTPS endpoint is allowed:
- Your production API
- Public webhook testing services
- Cloud function endpoints
For local development, use ngrok or Tailscale Funnel to expose your localhost with a public URL.
Timeouts
Forward requests have a 30-second timeout. If your endpoint doesn't respond within 30 seconds, the request is aborted and recorded as failed.
Why 30 seconds?
Most webhooks should process in under 1 second. A 30-second timeout is generous and prevents:
- Hanging connections
- Resource exhaustion
- Cascading failures
Handling slow endpoints
If your endpoint needs more than 30 seconds to process webhooks:
- Return quickly - Respond with 202 Accepted immediately
- Process asynchronously - Queue the work for background processing
- Use a callback - Have your app notify the original sender when done
This is the recommended pattern for webhook processing anyway—webhook senders expect fast responses.