{
  "openapi": "3.1.0",
  "info": {
    "title": "tronhooks API",
    "version": "2026-08",
    "description": "TRON deposit webhooks: watch USDT (TRC-20) / TRX addresses, receive HMAC-signed webhooks on confirmed transfers, reconcile via the events API. Docs: https://tronhooks.com/docs/",
    "contact": {
      "email": "ops@tronhooks.com",
      "url": "https://tronhooks.com"
    },
    "license": {
      "name": "Terms",
      "url": "https://tronhooks.com/docs/security/"
    }
  },
  "servers": [
    {
      "url": "https://tronhooks.com"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "watches"
    },
    {
      "name": "events"
    },
    {
      "name": "status"
    }
  ],
  "paths": {
    "/v1/watches": {
      "post": {
        "tags": [
          "watches"
        ],
        "summary": "Create a watch",
        "operationId": "createWatch",
        "description": "Register an address. Exactly one target: webhook_url (https endpoint you own) or telegram:true (Telegram connected in the console). Becomes effective within a few seconds; transfers before activation are not matched retroactively. Plan quota = active watches.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWatch"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. `secret` (whsec_…) is returned exactly once for webhook watches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WatchCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/QuotaExceeded"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Duplicate watch (same address, filter, channel and direction)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "tags": [
          "watches"
        ],
        "summary": "List watches",
        "operationId": "listWatches",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Watch"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/watches/{id}": {
      "delete": {
        "tags": [
          "watches"
        ],
        "summary": "Delete a watch (frees its quota slot)",
        "operationId": "deleteWatch",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/events": {
      "get": {
        "tags": [
          "events"
        ],
        "summary": "List events (reconciliation)",
        "operationId": "listEvents",
        "description": "Every matched transfer with its delivery status, newest first. Page backwards with `before` = previous response's `next_before`.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "before",
            "in": "query",
            "description": "created_at cursor (ISO-8601) — return events created before this instant",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "watch_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EventWithDelivery"
                      }
                    },
                    "next_before": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "data",
                    "next_before"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/healthz": {
      "get": {
        "tags": [
          "status"
        ],
        "summary": "Liveness + dependency checks",
        "operationId": "healthz",
        "security": [],
        "responses": {
          "200": {
            "description": "healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "network": {
                      "type": "string"
                    },
                    "checks": {
                      "type": "object",
                      "properties": {
                        "postgres": {
                          "type": "boolean"
                        },
                        "redis": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "a dependency is down (same body, ok=false)"
          }
        }
      }
    }
  },
  "webhooks": {
    "transfer.confirmed": {
      "post": {
        "summary": "Delivered to your webhook_url on every confirmed matching transfer",
        "description": "POST, JSON body, 10 s timeout; any 2xx acknowledges. Header `X-Signature` = hex(HMAC-SHA256(secret, raw body)). Retries after the immediate attempt: 1m / 5m / 30m / 2h / 6h, then dead-lettered (still visible via GET /v1/events). Deliveries are at-least-once — dedupe on event_id.",
        "parameters": [
          {
            "name": "X-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "acknowledged"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from the console: `Authorization: Bearer tw_live_…`. Keys are network-bound by prefix (mainnet-only deployment)."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid body/query (`details` carries field errors) or invalid_target",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "missing_api_key | invalid_api_key | revoked_api_key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "wrong_network — key prefix does not match this deployment",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "QuotaExceeded": {
        "description": "quota_exceeded — plan watch limit reached; body carries `usage`",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "type": "object",
                  "properties": {
                    "usage": {
                      "type": "object",
                      "properties": {
                        "active_watches": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "plan": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "RateLimited": {
        "description": "rate_limited — sliding 60 s window per key; headers X-RateLimit-Limit / X-RateLimit-Remaining / Retry-After",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "missing_api_key",
              "invalid_api_key",
              "revoked_api_key",
              "wrong_network",
              "quota_exceeded",
              "rate_limited",
              "invalid_target",
              "telegram_not_bound",
              "telegram_unavailable",
              "testnet_unsupported"
            ]
          },
          "details": {}
        },
        "required": [
          "error"
        ]
      },
      "CreateWatch": {
        "type": "object",
        "required": [
          "address",
          "asset_filter"
        ],
        "properties": {
          "address": {
            "type": "string",
            "description": "TRON address, base58 (T…) or 41-prefixed hex"
          },
          "asset_filter": {
            "type": "string",
            "enum": [
              "USDT",
              "TRX",
              "ALL"
            ]
          },
          "direction": {
            "type": "string",
            "enum": [
              "incoming",
              "outgoing",
              "both"
            ],
            "default": "incoming"
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "public https endpoint (private/reserved ranges rejected at create time and re-checked at delivery)"
          },
          "telegram": {
            "type": "boolean",
            "description": "deliver to the Telegram chat connected in the console (instead of webhook_url)"
          }
        }
      },
      "Watch": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "asset_filter": {
            "type": "string",
            "enum": [
              "USDT",
              "TRX",
              "ALL"
            ]
          },
          "direction": {
            "type": "string",
            "enum": [
              "incoming",
              "outgoing",
              "both"
            ]
          },
          "channel": {
            "type": "string",
            "enum": [
              "webhook",
              "telegram"
            ]
          },
          "webhook_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "ACTIVE",
              "PAUSED"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WatchCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Watch"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "whsec_… — shown once, stored encrypted; use it to verify X-Signature"
              },
              "note": {
                "type": "string"
              }
            }
          }
        ]
      },
      "Event": {
        "type": "object",
        "description": "Webhook payload / event record",
        "properties": {
          "api_version": {
            "type": "string",
            "example": "2026-08"
          },
          "event_id": {
            "type": "string",
            "description": "dedupe key (deliveries are at-least-once)"
          },
          "type": {
            "type": "string",
            "enum": [
              "transfer.confirmed"
            ]
          },
          "direction": {
            "type": "string",
            "enum": [
              "incoming",
              "outgoing"
            ]
          },
          "network": {
            "type": "string",
            "enum": [
              "mainnet",
              "nile"
            ]
          },
          "asset": {
            "type": "string",
            "enum": [
              "USDT",
              "TRX"
            ]
          },
          "contract": {
            "type": [
              "string",
              "null"
            ],
            "description": "TRC-20 contract (base58); null for native TRX"
          },
          "tx_id": {
            "type": "string"
          },
          "event_index": {
            "type": "integer",
            "description": "Transfer log index; native TRX is always 0"
          },
          "block_number": {
            "type": "integer"
          },
          "block_timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "amount_raw": {
            "type": "string",
            "description": "integer string in the smallest unit (6 decimals)"
          },
          "amount": {
            "type": "string",
            "description": "decimal string — never a float"
          },
          "watch_id": {
            "type": "string"
          }
        },
        "required": [
          "api_version",
          "event_id",
          "type",
          "direction",
          "network",
          "asset",
          "tx_id",
          "event_index",
          "block_number",
          "block_timestamp",
          "from",
          "to",
          "amount_raw",
          "amount",
          "watch_id"
        ]
      },
      "EventWithDelivery": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "delivery": {
                "type": "object",
                "properties": {
                  "notification_id": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "PENDING",
                      "DELIVERED",
                      "DEAD"
                    ]
                  },
                  "attempts": {
                    "type": "integer"
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        ]
      }
    }
  }
}
