> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runledger.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runledger.co/_mcp/server.

# List webhook events

GET https://api.runledger.co/v1/webhook_events

Events from the last 30 days, for catching up and reconciliation. Missed events are not replayed automatically: after an outage, or after an endpoint was disabled, filter by `delivery_status` or `endpoint` and send each one you need with `POST /v1/webhook_events/{webhook_event_id}/redeliver`.

Reference: https://docs.runledger.co/api-reference/webhook-events/list

## Authentication

- `Authorization` header (bearer token, required) — A secret key (`rl_sk_test_...`, `rl_sk_live_...`) or a restricted key with limited scopes (`rl_rk_test_...`, `rl_rk_live_...`). A restricted key can carry `agent: true` and an `expires_at`, and is what a coding agent should hold. Keys end in a checksum so secret scanners can recognize them. People create, roll and revoke keys in the dashboard; `GET /v1/api_keys` lists them, masked.

## Request

### Query parameters

- `type` (enum, optional) — Only events of this type.
  - Allowed values: `customer.created`, `customer.updated`, `subscription.created`, `subscription.updated`, `subscription.renewed`, `subscription.past_due`, `subscription.canceled`, `credit_grant.created`, `credit_grant.expired`, `credit_grant.voided`, `balance.depleted`, `hold.expired`, `outcome_charge.created`, `outcome_charge.disputed`, `outcome_charge.confirmed`, `outcome_charge.locked`, `outcome_charge.reversed`, `reversal.created`, `dispute.created`, `dispute.decision_due`, `dispute.resolved`, `invoice.created`, `invoice.finalized`, `invoice.paid`, `invoice.payment_failed`, `checkout_session.completed`, `checkout_session.expired`, `export.ready`, `export.failed`, `payment.disputed`, `reconciliation.completed`, `reconciliation.breaks_found`, `test_clock.ready`, `rate_card.activated`, `catalog.price_changed`, `margin_guardrail.breached`, `simulation.completed`, `usage.threshold_reached`, `balance.low`, `spend_limit.threshold_reached`, `spend_limit.updated`, `auto_top_up.succeeded`, `auto_top_up.failed`, `auto_top_up.disabled`, `credit_grant.expiring`, `proposal.pending`, `topup_chargeback.threshold_exceeded`
- `related_object` (string, optional) — Only events about this object ID.
- `delivery_status` (enum, optional) — Only events with this delivery status.
  - Allowed values: `pending`, `succeeded`, `failed`
- `endpoint` (string, optional) — Only events sent to this webhook endpoint (`we_...`).
- `created_after` (datetime, optional) — Only objects created at or after this time.
- `created_before` (datetime, optional) — Only objects created before this time.
- `limit` (integer, optional, default: 20) — Maximum number of objects to return, from 1 to 100.
- `cursor` (string, optional) — The `next_cursor` from the previous page.
- `include` (enum, optional) — Send `total_count` to add `total_count` to the list: how many objects match the filters across every page. The count is capped, and `total_count_approximate: true` says when the real number is higher. It is slower than a plain list, so ask for it when you show a number.
  - Allowed values: `total_count`

## Response

### 200

A page of webhook events.

- `object` ("list", required)
- `data` (list of WebhookEvent, required)
- `has_more` (boolean, required)
- `next_cursor` (string, optional)
- `total_count` (integer, optional) — Only with `include=total_count`. How many objects match the filters.
- `total_count_approximate` (boolean, optional) — Only with `include=total_count`. True when the count was capped and the real number is higher.

## Errors

### 400 Bad Request Error

The request was malformed or a parameter was invalid. A configuration that fails validation, such as two equally specific overrides with overlapping dates, returns `config_invalid`.

- `type` (string, required) — A URI for the error type. It links to the error codes table in the docs, where each `code` is listed with its cause. Branch on `code`, not on `type`.
- `title` (string, required) — A short summary of the error type.
- `status` (integer, required) — The HTTP status code.
- `code` (enum, required) — A stable, machine-readable error code. New codes may be added.
  - Allowed values: `invalid_request`, `parameter_missing`, `parameter_invalid`, `parameter_unknown`, `authentication_failed`, `permission_denied`, `insufficient_scope`, `proposal_required`, `config_invalid`, `livemode_mismatch`, `resource_not_found`, `resource_already_exists`, `idempotency_key_in_use`, `idempotency_key_reused`, `invalid_state`, `outcome_charge_locked`, `hold_not_active`, `insufficient_balance`, `credit_limit_exceeded`, `plan_locked`, `test_clock_advancing`, `provider_error`, `rate_limited`, `api_error`, `service_unavailable`
- `request_id` (string, required) — The ID of the request. Quote it to support, or look it up in request logs.
- `retryable` (boolean, required) — Whether sending the same request again, with the same idempotency key, may succeed.
- `detail` (string, optional) — An explanation of this occurrence. For people; don't parse it.
- `param` (string, optional) — The request parameter at fault, as a dotted path.
- `required_scope` (string, optional) — The scope the call needed, on `insufficient_scope`. Ask a person to widen the key, or reconnect with it.
- `errors` (list of ConfigIssue, optional) — Only with `config_invalid`: each problem the configuration document has, with the path of the part at fault, so an agent can fix the document without a second call.
- `blocked_by` (list of ErrorResponseBlockedByItems, optional) — On an `invalid_state` that names the objects in the way, the objects that must change first. Removing a payment provider lists every routing rule that uses it (`object: routing_rule`) and every customer whose own `payment_provider` names it (`object: customer`), in one list.
- `doc_url` (string, optional) — A link to the error codes table in the docs, where this `code` is listed with its cause.

### 401 Unauthorized Error

The API key is missing, invalid or revoked.

- `type` (string, required) — A URI for the error type. It links to the error codes table in the docs, where each `code` is listed with its cause. Branch on `code`, not on `type`.
- `title` (string, required) — A short summary of the error type.
- `status` (integer, required) — The HTTP status code.
- `code` (enum, required) — A stable, machine-readable error code. New codes may be added.
  - Allowed values: `invalid_request`, `parameter_missing`, `parameter_invalid`, `parameter_unknown`, `authentication_failed`, `permission_denied`, `insufficient_scope`, `proposal_required`, `config_invalid`, `livemode_mismatch`, `resource_not_found`, `resource_already_exists`, `idempotency_key_in_use`, `idempotency_key_reused`, `invalid_state`, `outcome_charge_locked`, `hold_not_active`, `insufficient_balance`, `credit_limit_exceeded`, `plan_locked`, `test_clock_advancing`, `provider_error`, `rate_limited`, `api_error`, `service_unavailable`
- `request_id` (string, required) — The ID of the request. Quote it to support, or look it up in request logs.
- `retryable` (boolean, required) — Whether sending the same request again, with the same idempotency key, may succeed.
- `detail` (string, optional) — An explanation of this occurrence. For people; don't parse it.
- `param` (string, optional) — The request parameter at fault, as a dotted path.
- `required_scope` (string, optional) — The scope the call needed, on `insufficient_scope`. Ask a person to widen the key, or reconnect with it.
- `errors` (list of ConfigIssue, optional) — Only with `config_invalid`: each problem the configuration document has, with the path of the part at fault, so an agent can fix the document without a second call.
- `blocked_by` (list of ErrorResponseBlockedByItems, optional) — On an `invalid_state` that names the objects in the way, the objects that must change first. Removing a payment provider lists every routing rule that uses it (`object: routing_rule`) and every customer whose own `payment_provider` names it (`object: customer`), in one list.
- `doc_url` (string, optional) — A link to the error codes table in the docs, where this `code` is listed with its cause.

### 403 Forbidden Error

The key can't do this. A restricted or agent key lacks the scope (`insufficient_scope`, with `required_scope`), a live key called a sandbox-only endpoint, an agent tried a live config write that needs a proposal (`proposal_required`), or something only a signed-in person can do.

- `type` (string, required) — A URI for the error type. It links to the error codes table in the docs, where each `code` is listed with its cause. Branch on `code`, not on `type`.
- `title` (string, required) — A short summary of the error type.
- `status` (integer, required) — The HTTP status code.
- `code` (enum, required) — A stable, machine-readable error code. New codes may be added.
  - Allowed values: `invalid_request`, `parameter_missing`, `parameter_invalid`, `parameter_unknown`, `authentication_failed`, `permission_denied`, `insufficient_scope`, `proposal_required`, `config_invalid`, `livemode_mismatch`, `resource_not_found`, `resource_already_exists`, `idempotency_key_in_use`, `idempotency_key_reused`, `invalid_state`, `outcome_charge_locked`, `hold_not_active`, `insufficient_balance`, `credit_limit_exceeded`, `plan_locked`, `test_clock_advancing`, `provider_error`, `rate_limited`, `api_error`, `service_unavailable`
- `request_id` (string, required) — The ID of the request. Quote it to support, or look it up in request logs.
- `retryable` (boolean, required) — Whether sending the same request again, with the same idempotency key, may succeed.
- `detail` (string, optional) — An explanation of this occurrence. For people; don't parse it.
- `param` (string, optional) — The request parameter at fault, as a dotted path.
- `required_scope` (string, optional) — The scope the call needed, on `insufficient_scope`. Ask a person to widen the key, or reconnect with it.
- `errors` (list of ConfigIssue, optional) — Only with `config_invalid`: each problem the configuration document has, with the path of the part at fault, so an agent can fix the document without a second call.
- `blocked_by` (list of ErrorResponseBlockedByItems, optional) — On an `invalid_state` that names the objects in the way, the objects that must change first. Removing a payment provider lists every routing rule that uses it (`object: routing_rule`) and every customer whose own `payment_provider` names it (`object: customer`), in one list.
- `doc_url` (string, optional) — A link to the error codes table in the docs, where this `code` is listed with its cause.

### 429 Too Many Requests Error

Too many requests. Wait for the number of seconds in `Retry-After`.

- `type` (string, required) — A URI for the error type. It links to the error codes table in the docs, where each `code` is listed with its cause. Branch on `code`, not on `type`.
- `title` (string, required) — A short summary of the error type.
- `status` (integer, required) — The HTTP status code.
- `code` (enum, required) — A stable, machine-readable error code. New codes may be added.
  - Allowed values: `invalid_request`, `parameter_missing`, `parameter_invalid`, `parameter_unknown`, `authentication_failed`, `permission_denied`, `insufficient_scope`, `proposal_required`, `config_invalid`, `livemode_mismatch`, `resource_not_found`, `resource_already_exists`, `idempotency_key_in_use`, `idempotency_key_reused`, `invalid_state`, `outcome_charge_locked`, `hold_not_active`, `insufficient_balance`, `credit_limit_exceeded`, `plan_locked`, `test_clock_advancing`, `provider_error`, `rate_limited`, `api_error`, `service_unavailable`
- `request_id` (string, required) — The ID of the request. Quote it to support, or look it up in request logs.
- `retryable` (boolean, required) — Whether sending the same request again, with the same idempotency key, may succeed.
- `detail` (string, optional) — An explanation of this occurrence. For people; don't parse it.
- `param` (string, optional) — The request parameter at fault, as a dotted path.
- `required_scope` (string, optional) — The scope the call needed, on `insufficient_scope`. Ask a person to widen the key, or reconnect with it.
- `errors` (list of ConfigIssue, optional) — Only with `config_invalid`: each problem the configuration document has, with the path of the part at fault, so an agent can fix the document without a second call.
- `blocked_by` (list of ErrorResponseBlockedByItems, optional) — On an `invalid_state` that names the objects in the way, the objects that must change first. Removing a payment provider lists every routing rule that uses it (`object: routing_rule`) and every customer whose own `payment_provider` names it (`object: customer`), in one list.
- `doc_url` (string, optional) — A link to the error codes table in the docs, where this `code` is listed with its cause.

### 500 Internal Server Error

Something went wrong on Runledger's side. Safe to retry with the same idempotency key.

- `type` (string, required) — A URI for the error type. It links to the error codes table in the docs, where each `code` is listed with its cause. Branch on `code`, not on `type`.
- `title` (string, required) — A short summary of the error type.
- `status` (integer, required) — The HTTP status code.
- `code` (enum, required) — A stable, machine-readable error code. New codes may be added.
  - Allowed values: `invalid_request`, `parameter_missing`, `parameter_invalid`, `parameter_unknown`, `authentication_failed`, `permission_denied`, `insufficient_scope`, `proposal_required`, `config_invalid`, `livemode_mismatch`, `resource_not_found`, `resource_already_exists`, `idempotency_key_in_use`, `idempotency_key_reused`, `invalid_state`, `outcome_charge_locked`, `hold_not_active`, `insufficient_balance`, `credit_limit_exceeded`, `plan_locked`, `test_clock_advancing`, `provider_error`, `rate_limited`, `api_error`, `service_unavailable`
- `request_id` (string, required) — The ID of the request. Quote it to support, or look it up in request logs.
- `retryable` (boolean, required) — Whether sending the same request again, with the same idempotency key, may succeed.
- `detail` (string, optional) — An explanation of this occurrence. For people; don't parse it.
- `param` (string, optional) — The request parameter at fault, as a dotted path.
- `required_scope` (string, optional) — The scope the call needed, on `insufficient_scope`. Ask a person to widen the key, or reconnect with it.
- `errors` (list of ConfigIssue, optional) — Only with `config_invalid`: each problem the configuration document has, with the path of the part at fault, so an agent can fix the document without a second call.
- `blocked_by` (list of ErrorResponseBlockedByItems, optional) — On an `invalid_state` that names the objects in the way, the objects that must change first. Removing a payment provider lists every routing rule that uses it (`object: routing_rule`) and every customer whose own `payment_provider` names it (`object: customer`), in one list.
- `doc_url` (string, optional) — A link to the error codes table in the docs, where this `code` is listed with its cause.

## Types

### WebhookEvent

A thin event: what happened and which object it happened to. Fetch the object for its current state. Deduplicate on `id`.

- `id` (string, required)
- `object` ("webhook_event", required)
- `type` (enum, required) — New types may be added. Subscribe only to the ones you handle.
  - Allowed values: `customer.created`, `customer.updated`, `subscription.created`, `subscription.updated`, `subscription.renewed`, `subscription.past_due`, `subscription.canceled`, `credit_grant.created`, `credit_grant.expired`, `credit_grant.voided`, `balance.depleted`, `hold.expired`, `outcome_charge.created`, `outcome_charge.disputed`, `outcome_charge.confirmed`, `outcome_charge.locked`, `outcome_charge.reversed`, `reversal.created`, `dispute.created`, `dispute.decision_due`, `dispute.resolved`, `invoice.created`, `invoice.finalized`, `invoice.paid`, `invoice.payment_failed`, `checkout_session.completed`, `checkout_session.expired`, `export.ready`, `export.failed`, `payment.disputed`, `reconciliation.completed`, `reconciliation.breaks_found`, `test_clock.ready`, `rate_card.activated`, `catalog.price_changed`, `margin_guardrail.breached`, `simulation.completed`, `usage.threshold_reached`, `balance.low`, `spend_limit.threshold_reached`, `spend_limit.updated`, `auto_top_up.succeeded`, `auto_top_up.failed`, `auto_top_up.disabled`, `credit_grant.expiring`, `proposal.pending`, `topup_chargeback.threshold_exceeded`
- `api_version` (string, required)
- `related_object` (WebhookEventRelatedObject, required)
- `livemode` (boolean, required)
- `created_at` (datetime, required)
- `reason` (WebhookEventReason, optional) — What caused the event.
- `context` (WebhookEventContext, optional) — The fields that identify what a limit, window or purchase event is about, for events that don't name a stored object of their own. Fetch `related_object` for the current state.
- `delivery_status` (enum, optional) — `pending`: still being sent or retried. `succeeded`: every subscribed endpoint acknowledged it. `failed`: retries ran out for at least one endpoint.
  - Allowed values: `pending`, `succeeded`, `failed`
- `deliveries` (list of WebhookEventDeliveriesItems, optional) — Retrieve only. The last 10 attempts per endpoint.

### ConfigIssue

A problem or warning about a part of a configuration document.

- `code` (string, required) — A stable code. New codes may be added.
- `message` (string, required)
- `path` (string, optional) — Where in the document, as a dotted path.

### ErrorResponseBlockedByItems

- `object` (enum, required) — The kind of object in the way: a routing rule, or a customer with a `payment_provider` override.
  - Allowed values: `routing_rule`, `customer`
- `id` (string, required) — The object's ID, `rr_...` for a routing rule and `cus_...` for a customer.

### WebhookEventRelatedObject

- `id` (string, required)
- `object` (string, required)
- `url` (string, required) — The API path to fetch it.

### WebhookEventReason

What caused the event.

- `type` (enum, optional) — `request`: an API call. `rule`: a reversal rule, or a notification rule's lead time or threshold. `clock`: a window, horizon, deadline or period. `system`: Runledger, such as a reconciliation.
  - Allowed values: `request`, `rule`, `clock`, `system`
- `request_id` (string, optional)
- `idempotency_key` (string, optional) — The idempotency key of the causing request, to match events to your own submissions.
- `notification_rule` (string, optional) — When a notification rule's lead time or threshold made the event: the rule (`nr_...`). Such an event goes to the rule's endpoint alone, not to the endpoints subscribed to its type.

### WebhookEventContext

The fields that identify what a limit, window or purchase event is about, for events that don't name a stored object of their own. Fetch `related_object` for the current state.

- `customer` (string, optional)
- `member_id` (string, optional)
- `team_id` (string, optional)
- `pool` (string, optional)
- `allowance` (string, optional) — The allowance key.
- `spend_limit` (string, optional) — The spend limit ID.
- `threshold` (string, optional) — The `alert_at` value that was reached, as a decimal from 0 to 1.
- `window_start` (datetime, optional)
- `resets_at` (datetime, optional)

### WebhookEventDeliveriesItems

- `webhook_endpoint` (string, required)
- `attempted_at` (datetime, required)
- `succeeded` (boolean, required)
- `status_code` (integer, optional)
- `duration_ms` (integer, optional)

## Examples

**Response**

```json
{
  "object": "string",
  "data": [
    {
      "id": "whe_0pL3kJ6hGf",
      "object": "string",
      "type": "customer.created",
      "api_version": "2026-10-18",
      "related_object": {
        "id": "oc_2xK9cL4vRt",
        "object": "outcome_charge",
        "url": "/v1/outcome_charges/oc_2xK9cL4vRt"
      },
      "livemode": true,
      "created_at": "2024-01-15T09:30:00Z",
      "reason": {
        "type": "request",
        "request_id": "string",
        "idempotency_key": "string",
        "notification_rule": "string"
      },
      "context": {
        "customer": "string",
        "member_id": "string",
        "team_id": "string",
        "pool": "string",
        "allowance": "string",
        "spend_limit": "string",
        "threshold": "0.8",
        "window_start": "2024-01-15T09:30:00Z",
        "resets_at": "2024-01-15T09:30:00Z"
      },
      "delivery_status": "pending",
      "deliveries": [
        {
          "webhook_endpoint": "string",
          "attempted_at": "2024-01-15T09:30:00Z",
          "succeeded": true,
          "status_code": 1,
          "duration_ms": 1
        }
      ]
    }
  ],
  "has_more": true,
  "next_cursor": "string",
  "total_count": 1,
  "total_count_approximate": true
}
```

**SDK Code**

```python
import requests

url = "https://api.runledger.co/v1/webhook_events"

headers = {"Authorization": "Bearer <api_key>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.runledger.co/v1/webhook_events';
const options = {method: 'GET', headers: {Authorization: 'Bearer <api_key>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.runledger.co/v1/webhook_events"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <api_key>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.runledger.co/v1/webhook_events")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <api_key>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.runledger.co/v1/webhook_events")
  .header("Authorization", "Bearer <api_key>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.runledger.co/v1/webhook_events', [
  'headers' => [
    'Authorization' => 'Bearer <api_key>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.runledger.co/v1/webhook_events");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <api_key>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <api_key>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.runledger.co/v1/webhook_events")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```