> For the complete documentation index, see [llms.txt](https://docs.tumbler.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tumbler.app/features/acc-callbacks.md).

# \[PAID] ACC callbacks: events and delivery

## Who this is for

Providers who enable Advanced Connection Control and want to receive limit-exceeded events.

## What you can do

* Configure a callback endpoint through metadata.
* Verify the signature of every event.
* Handle soft/hard events idempotently with retries in mind.

## Examples

* `acc.limit.soft_reached` - limit reached, connection allowed.
* `acc.limit.hard_denied` - limit reached, connection denied.
* `X-Callback-Signature` - HMAC signature of the event body.

## See also

* [advanced-connection-control.md](/features/advanced-connection-control.md)
* [features/overview.md](/features/overview.md)
* [security/fetcher-restrictions.md](/after-first-launch/fetcher-restrictions.md)

## Configuration

Callbacks are enabled with subscription or node metadata fields:

* `acc-callback-url: https://...` - event receiver address; HTTPS only.
* `acc-callback-secret: <string>` - shared signature secret; store and rotate it as a credential.
* `acc-callback-events: limit_soft,limit_hard` - optional comma-separated list.

If `acc-callback-events` is not set, all ACC events are sent.

Value mapping:

| Value in `acc-callback-events` | Event type               |
| ------------------------------ | ------------------------ |
| `limit_soft`                   | `acc.limit.soft_reached` |
| `limit_hard`                   | `acc.limit.hard_denied`  |

## When it is called

* On a connection attempt that exceeds `acc-connection-limit` in the selected scope: `subscription`, `config`, or `group`.
* In `soft` mode, the event records the over-limit state but the connection is allowed.
* In `hard` mode, the event accompanies a connection denial.

Callbacks are sent only when ACC is active. If ACC is disabled or the capability is not allowed by the plan, callback fields are ignored.

## Security and signature

* Only HTTPS URLs are accepted.
* The URL must satisfy outbound/fetcher safety restrictions; do not use localhost, private IPs, non-standard schemes, or userinfo.
* Every `POST` contains headers:
  * `X-Callback-Timestamp-Ms` - Unix time in milliseconds when the signature was created.
  * `X-Callback-Id` - event UUID; same for all delivery retries.
  * `X-Callback-Signature` - `base64( HMAC-SHA256(secret, timestamp + "\n" + sha256(body)) )`, where:
    * `timestamp` - string from `X-Callback-Timestamp-Ms`;
    * `sha256(body)` - SHA-256 hex string of the raw JSON body.

The provider must verify the signature and timestamp freshness. Return `2xx` only after successful verification and processing.

## Idempotency

`X-Callback-Id` is unique for the logical event and does not change during retries. The endpoint must deduplicate repeated deliveries with the same ID and safely return the same result.

## Event types

* `acc.limit.soft_reached` - limit reached, `soft` mode, connection allowed.
* `acc.limit.hard_denied` - limit reached, `hard` mode, connection denied.

## Event body

Minimum field set:

* `event_type` - event type.
* `occurred_at_ms` - Unix time in milliseconds when the event was recorded.
* `provider_domain` - provider domain.
* `subscription_id` - subscription identifier.
* `scope` - `subscription`, `config`, or `group`.
* `group` / `cid` - included when applicable to the scope.
* `installation_id` - pseudonymous installation UUID.
* `device_type` - device type as sent by the client.
* `active_connections` - current active connection count after the event.
* `limit` - configured threshold for the selected scope.
* `node_id` - node, if known.
* `reason` - reason code, for example `connection_limit_exceeded`.

Example:

```json
{
  "event_type": "acc.limit.hard_denied",
  "occurred_at_ms": 1700000000123,
  "provider_domain": "example.com",
  "subscription_id": "sub_123",
  "scope": "group",
  "group": "eu",
  "cid": "stable-config-id",
  "installation_id": "1c1fc3a9-2f7e-4db8-9c15-9e8b6f5c1d00",
  "device_type": "ios",
  "active_connections": 3,
  "limit": 2,
  "node_id": "edge-eu-1",
  "reason": "connection_limit_exceeded"
}
```

## Scope identifiers

* VLESS: `group` and `cid` are read from query `group=<...>` and `cid=<...>` and stored in `node.meta`.
* JSON: `meta.group` and `meta.cid` are passed into the event scope.

## Delivery policy

* Delivery is `at-least-once`.
* The same `X-Callback-Id` is reused on retries.
* Retries happen on network timeouts, `5xx`, or `429`.
* Up to 10 attempts over 24 hours.
* Exponential backoff: seconds, then minutes, capped at about 30 minutes between attempts.
* After attempts are exhausted or 24 hours pass, the event is marked dead; no further retries are made.

## Response expectations

* `2xx` - accepted, no further retries.
* `4xx`, except `429` - permanent error; event is marked dead without retries.
* `429` or `5xx` - temporary error; another attempt will be made.
* Response must be fast and must not require session cookies.

## Compatibility

* Ignore unknown fields for forward compatibility.
* New fields may be added without changing existing keys.
* Do not change the semantics of `X-Callback-Id`: it is the idempotency key.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.tumbler.app/features/acc-callbacks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
