API reference
Webhooks
Outgoing webhook subscriptions and delivery attempts.
- GET /webhooks
- POST /webhooks
- GET /webhooks/{id}
- PATCH /webhooks/{id}
- DELETE /webhooks/{id}
- PUT /webhooks/{id}/enable
- PUT /webhooks/{id}/disable
- PUT /webhooks/{id}/rotate_secret
- PUT /webhooks/{id}/reset_circuit
- GET /webhooks/{id}/event_callbacks
- POST /webhooks/{id}/event_callbacks/{callback_id}/redeliver
List webhooks for the authenticated account
GET /webhooks
Bearer JWT (Sign-In With Ethereum)
Query parameters
-
page integer
1-based page number.
-
per_page integer
Items per page (capped at 100).
-
sort string
Comma-separated sort fields; prefix with - for descending (e.g. -created_at,status). An unsupported field is rejected with 422
invalid_sort, whose detail names the allowed set — it is never silently dropped. -
topic string
Filter to subscriptions that include this topic.
One of:payments.created,payments.signed,payments.authorized,payments.charged,payments.captured,payments.voided,payments.released,payments.refunded,payments.authorization_expiring,payments.expired,payments.failed,payments.disputed,payments.dispute_closed -
active boolean
Filter by active status.
-
circuit_state string
Filter by circuit state.
One of:closed,open
Responses
- 200 Webhooks.
- 401 Authentication failed.
curl "https://api.rail0.xyz/webhooks" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks", { headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks")req = Net::HTTP::Get.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks" req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}[ { "id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ], "active": false, "circuit_state": "closed", "circuit_failure_count": 1, "created_at": "2026-10-01T12:00:00Z", "updated_at": "2026-10-01T12:00:00Z" }]Create a webhook
POST /webhooks
Bearer JWT (Sign-In With Ethereum)
Request body
-
name string required
Human-readable identifier.
-
callback_url string required
HTTPS destination URL.
-
topics array of string required
Event topics for this subscription — one secret and one circuit breaker for all of them. Repeated values are collapsed.
One of:payments.created,payments.signed,payments.authorized,payments.charged,payments.captured,payments.voided,payments.released,payments.refunded,payments.authorization_expiring,payments.expired,payments.failed,payments.disputed,payments.dispute_closed
Responses
-
201
Webhook created.
shared_secretis returned only on this response and on rotate. - 401 Authentication failed.
-
422
validation_failed(a malformed callback_url or topic), orwebhook_limit_reached: the account already holds 20 subscriptions (WEBHOOKS_PER_ACCOUNT; disabled ones count, deleted ones do not). Delete one, or add topics to an existing subscription.
curl -X POST "https://api.rail0.xyz/webhooks" \ -H "Authorization: Bearer $RAIL0_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ]}'const res = await fetch("https://api.rail0.xyz/webhooks", { method: "POST", headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Acme Store", callback_url: "https://acme.example/webhooks/rail0", topics: [ "payments.captured" ] }),});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks")req = Net::HTTP::Post.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"req["Content-Type"] = "application/json"req.body = { name: "Acme Store", callback_url: "https://acme.example/webhooks/rail0", topics: [ "payments.captured" ]}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os" "strings")
func main() { body := strings.NewReader(`{ "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ]}`) url := "https://api.rail0.xyz/webhooks" req, err := http.NewRequest(http.MethodPost, url, body) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN")) req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}{ "id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ], "active": false, "circuit_state": "closed", "circuit_failure_count": 1, "created_at": "2026-10-01T12:00:00Z", "updated_at": "2026-10-01T12:00:00Z", "shared_secret": "string"}Get a webhook
GET /webhooks/{id}
Bearer JWT (Sign-In With Ethereum)
Path parameters
-
id string (uuid) required
Responses
- 200 Webhook.
- 403 Caller not permitted.
- 404 Resource not found.
curl "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", { headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b")req = Net::HTTP::Get.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b" req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}{ "id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ], "active": false, "circuit_state": "closed", "circuit_failure_count": 1, "created_at": "2026-10-01T12:00:00Z", "updated_at": "2026-10-01T12:00:00Z"}Update a webhook (name, callback_url, topics)
PATCH /webhooks/{id}
Bearer JWT (Sign-In With Ethereum)
Path parameters
-
id string (uuid) required
Request body (optional)
-
name string
-
callback_url string
-
topics array of string
REPLACES the whole set, so this is also how a topic is removed. The shared secret is untouched.
One of:payments.created,payments.signed,payments.authorized,payments.charged,payments.captured,payments.voided,payments.released,payments.refunded,payments.authorization_expiring,payments.expired,payments.failed,payments.disputed,payments.dispute_closed
Responses
- 200 Webhook updated.
- 403 Caller not permitted.
- 404 Resource not found.
- 422 Validation error.
curl -X PATCH "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b")req = Net::HTTP::Patch.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b" req, err := http.NewRequest(http.MethodPatch, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}{ "id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ], "active": false, "circuit_state": "closed", "circuit_failure_count": 1, "created_at": "2026-10-01T12:00:00Z", "updated_at": "2026-10-01T12:00:00Z"}Delete a webhook
DELETE /webhooks/{id}
Bearer JWT (Sign-In With Ethereum)
Path parameters
-
id string (uuid) required
Responses
- 204 Webhook deleted.
- 403 Caller not permitted.
- 404 Resource not found.
curl -X DELETE "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", { method: "DELETE", headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b")req = Net::HTTP::Delete.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b" req, err := http.NewRequest(http.MethodDelete, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}Enable a webhook
PUT /webhooks/{id}/enable
Bearer JWT (Sign-In With Ethereum)
Path parameters
-
id string (uuid) required
Responses
- 200 Webhook enabled.
- 403 Caller not permitted.
- 404 Resource not found.
curl -X PUT "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/enable" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/enable", { method: "PUT", headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/enable")req = Net::HTTP::Put.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/enable" req, err := http.NewRequest(http.MethodPut, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}{ "id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ], "active": false, "circuit_state": "closed", "circuit_failure_count": 1, "created_at": "2026-10-01T12:00:00Z", "updated_at": "2026-10-01T12:00:00Z"}Disable a webhook
PUT /webhooks/{id}/disable
Bearer JWT (Sign-In With Ethereum)
Path parameters
-
id string (uuid) required
Responses
- 200 Webhook disabled.
- 403 Caller not permitted.
- 404 Resource not found.
curl -X PUT "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/disable" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/disable", { method: "PUT", headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/disable")req = Net::HTTP::Put.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/disable" req, err := http.NewRequest(http.MethodPut, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}{ "id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ], "active": false, "circuit_state": "closed", "circuit_failure_count": 1, "created_at": "2026-10-01T12:00:00Z", "updated_at": "2026-10-01T12:00:00Z"}Rotate the shared secret — returns the new secret once
PUT /webhooks/{id}/rotate_secret
Bearer JWT (Sign-In With Ethereum)
Replaces the shared secret and returns the new one once. The replaced key is kept for a grace window (one hour): inside it every delivery carries X-Rail0-Signature under the new key AND X-Rail0-Signature-Previous under the old one, so an endpoint verifies with whichever key it has deployed and no delivery in flight at rotation becomes unverifiable. The old key signs only, is never honoured past the window, and a second rotation inside it drops the oldest key. Two concurrent rotations cannot both succeed: the one that loses the race answers 409.
Path parameters
-
id string (uuid) required
Responses
-
200
Secret rotated; the new
shared_secretis returned once. - 403 Caller not permitted.
- 404 Resource not found.
-
409
conflict: another rotation of this webhook replaced the secret while this request was in flight. No secret is returned; the winning request's secret is the current one.
curl -X PUT "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/rotate_secret" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/rotate_secret", { method: "PUT", headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/rotate_secret")req = Net::HTTP::Put.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/rotate_secret" req, err := http.NewRequest(http.MethodPut, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}{ "id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ], "active": false, "circuit_state": "closed", "circuit_failure_count": 1, "created_at": "2026-10-01T12:00:00Z", "updated_at": "2026-10-01T12:00:00Z", "shared_secret": "string"}Reset the circuit breaker and re-enable the webhook
PUT /webhooks/{id}/reset_circuit
Bearer JWT (Sign-In With Ethereum)
Path parameters
-
id string (uuid) required
Responses
- 200 Circuit reset.
- 403 Caller not permitted.
- 404 Resource not found.
curl -X PUT "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/reset_circuit" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/reset_circuit", { method: "PUT", headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/reset_circuit")req = Net::HTTP::Put.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/reset_circuit" req, err := http.NewRequest(http.MethodPut, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}{ "id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "name": "Acme Store", "callback_url": "https://acme.example/webhooks/rail0", "topics": [ "payments.captured" ], "active": false, "circuit_state": "closed", "circuit_failure_count": 1, "created_at": "2026-10-01T12:00:00Z", "updated_at": "2026-10-01T12:00:00Z"}List delivery attempts for a webhook
GET /webhooks/{id}/event_callbacks
Bearer JWT (Sign-In With Ethereum)
Path parameters
-
id string (uuid) required
Query parameters
-
page integer
1-based page number.
-
per_page integer
Items per page (capped at 100).
-
sort string
Comma-separated sort fields; prefix with - for descending (e.g. -created_at,status). An unsupported field is rejected with 422
invalid_sort, whose detail names the allowed set — it is never silently dropped. -
status string
Filter by delivery status.
One of:delivered,failed -
topic string
Filter by event topic.
One of:payments.created,payments.signed,payments.authorized,payments.charged,payments.captured,payments.voided,payments.released,payments.refunded,payments.authorization_expiring,payments.expired,payments.failed,payments.disputed,payments.dispute_closed -
payment_id string
Filter by the payment the delivery is for: its id (UUID) or its rail0_id (0x…).
-
response_code string
Filter by the subscriber's exact HTTP response code (e.g. 500).
-
since string (date-time)
Only deliveries at/after this time (ISO-8601).
-
until string (date-time)
Only deliveries at/before this time (ISO-8601).
Responses
- 200 Event callbacks.
- 403 Caller not permitted.
- 404 Resource not found.
curl "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/event_callbacks?status=failed" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/event_callbacks?status=failed", { headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/event_callbacks?status=failed")req = Net::HTTP::Get.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/event_callbacks?status=failed" req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}[ { "id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "webhook_id": "0f1e2d3c-4b5a-4968-8776-655443322110", "payment_id": "5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b", "topic": "payments.captured", "callback_url": "https://acme.example/webhooks/rail0", "response_code": null, "response_message": null, "error_reason": null, "status": "delivered", "created_at": "2026-10-01T12:00:00Z", "payload": null }]Re-deliver a recorded event callback's exact payload
POST /webhooks/{id}/event_callbacks/{callback_id}/redeliver
Bearer JWT (Sign-In With Ethereum)
Re-sends the stored delivery payload verbatim (same embedded event id, so a consumer that already processed it deduplicates) under a fresh timestamped signature, through the standard async delivery job. A disabled or circuit-open webhook drops the replay like any delivery - reset the circuit first.
Path parameters
-
id string (uuid) required
-
callback_id string (uuid) required
The event_callback row whose recorded payload to re-send.
Responses
- 202 Replay queued.
- 401 Authentication failed.
- 403 Caller not permitted.
- 404 Resource not found.
-
503
queue_unavailable: the job queue could not be reached, so nothing was enqueued and no replay will happen. Nothing is written by this endpoint; retry in a moment.
curl -X POST "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/event_callbacks/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/redeliver" \ -H "Authorization: Bearer $RAIL0_TOKEN"const res = await fetch("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/event_callbacks/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/redeliver", { method: "POST", headers: { Authorization: `Bearer ${process.env.RAIL0_TOKEN}`, },});
if (!res.ok) { const { code, detail } = await res.json(); throw new Error(`${res.status} ${code}: ${detail}`);}const data = await res.json();require "json"require "net/http"
uri = URI("https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/event_callbacks/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/redeliver")req = Net::HTTP::Post.new(uri)req["Authorization"] = "Bearer #{ENV.fetch("RAIL0_TOKEN")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)data = JSON.parse(res.body)package main
import ( "fmt" "io" "net/http" "os")
func main() { url := "https://api.rail0.xyz/webhooks/5f0c2b1e-8d4a-4f6e-9a7b-2c3d4e5f6a7b/event_callbacks/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/redeliver" req, err := http.NewRequest(http.MethodPost, url, nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("RAIL0_TOKEN"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, err := io.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(res.Status, string(data))}{ "status": "queued"}