{
  "openapi": "3.1.0",
  "info": {
    "title": "Вебхуки X-Hub",
    "version": "1"
  },
  "webhooks": {
    "payment.status_changed": {
      "post": {
        "operationId": "paymentStatusChanged",
        "summary": "Изменился статус платежа (включая списания по подписке)",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "payment.delivery_proof_reminder": {
      "post": {
        "operationId": "paymentDeliveryProofReminder",
        "summary": "По оплаченному платежу ещё не передан хэш перевода клиенту",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentDeliveryProofReminderPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "withdrawal.status_changed": {
      "post": {
        "operationId": "withdrawalStatusChanged",
        "summary": "Изменился статус выплаты",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WithdrawalPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "exchange.status_changed": {
      "post": {
        "operationId": "exchangeStatusChanged",
        "summary": "Изменился статус обмена",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExchangePayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "subscription.status_changed": {
      "post": {
        "operationId": "subscriptionStatusChanged",
        "summary": "Изменился статус подписки или подготовилась ссылка привязки",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "subscription.charge_exhausted": {
      "post": {
        "operationId": "subscriptionChargeExhausted",
        "summary": "Бюджет повторов списания по подписке исчерпан — до даты next_allowed_at новых попыток не будет",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionChargeExhaustedPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "refund.completed": {
      "post": {
        "operationId": "refundCompleted",
        "summary": "Возврат исполнен — деньги ушли клиенту",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefundPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "refund.rejected": {
      "post": {
        "operationId": "refundRejected",
        "summary": "Заявка на возврат отклонена — деньги не двигались",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefundPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "refund.failed": {
      "post": {
        "operationId": "refundFailed",
        "summary": "Возврат сорвался при исполнении — деньги не ушли",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefundPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "refund.reversed": {
      "post": {
        "operationId": "refundReversed",
        "summary": "Исполненный возврат откачен — платёж снова живой",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefundPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "kyc.status_changed": {
      "post": {
        "operationId": "kycStatusChanged",
        "summary": "Изменился статус проверки клиента",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    },
    "gateway.status_changed": {
      "post": {
        "operationId": "gatewayStatusChanged",
        "summary": "Изменился статус платёжного шлюза X-Hub",
        "security": [
          {
            "webhookSignature": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GatewayPayload"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Доставка принята. Любой 2xx закрывает её; всё остальное и таймаут — повтор по лестнице ретраев."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "webhookSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Webhook-Signature",
        "description": "sha256=<hex>. HMAC-SHA256 от строки \"<X-Webhook-Timestamp>.<тело как есть>\" на секрете вебхуков мерчанта. Рядом приходят X-Webhook-Id и X-Webhook-Timestamp."
      }
    },
    "schemas": {
      "PaymentPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "const": "payment.status_changed"
          },
          "payment_id": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "previous_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount_rub": {
            "type": "string"
          },
          "actual_amount_rub": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount_usdt": {
            "type": [
              "string",
              "null"
            ]
          },
          "payer_name": {
            "type": "string"
          },
          "payment_method": {
            "type": "string"
          },
          "subscription_id": {
            "type": "string"
          },
          "period_ref": {
            "type": "string"
          },
          "refund_pending": {
            "type": "boolean"
          },
          "failure_reason": {
            "type": "string",
            "enum": [
              "insufficient_funds",
              "declined_by_bank",
              "mandate_revoked",
              "cancelled_by_customer",
              "cancelled_by_bank",
              "expired",
              "unknown"
            ]
          },
          "timestamp": {
            "type": "string"
          },
          "metadata": {
            "anyOf": [
              {
                "type": "object",
                "additionalProperties": {}
              },
              {
                "type": "null"
              }
            ]
          },
          "sandbox": {
            "type": "boolean"
          },
          "shop_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "shop_name": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "event",
          "payment_id",
          "external_id",
          "status",
          "previous_status",
          "amount_rub",
          "actual_amount_rub",
          "amount_usdt",
          "payment_method",
          "refund_pending",
          "timestamp",
          "metadata"
        ],
        "additionalProperties": false
      },
      "PaymentDeliveryProofReminderPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "const": "payment.delivery_proof_reminder"
          },
          "payment_id": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_at": {
            "type": "string"
          },
          "reminder": {
            "type": "number",
            "enum": [
              1,
              2,
              3
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "waiting",
              "rejected"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "not_found_or_unconfirmed",
              "already_used",
              "invalid"
            ]
          },
          "timestamp": {
            "type": "string"
          },
          "shop_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "shop_name": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "event",
          "payment_id",
          "external_id",
          "paid_at",
          "reminder",
          "status",
          "timestamp"
        ],
        "additionalProperties": false
      },
      "WithdrawalPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "const": "withdrawal.status_changed"
          },
          "withdrawal_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "previous_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount_usdt": {
            "type": "string"
          },
          "wallet_address": {
            "type": "string"
          },
          "tx_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "timestamp": {
            "type": "string"
          }
        },
        "required": [
          "event",
          "withdrawal_id",
          "status",
          "previous_status",
          "amount_usdt",
          "wallet_address",
          "tx_hash",
          "timestamp"
        ],
        "additionalProperties": false
      },
      "ExchangePayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string"
          },
          "exchange_id": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "direction": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "previous_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount_usdt": {
            "type": "string"
          },
          "amount_usdt_received": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount_rub": {
            "type": "string"
          },
          "rate": {
            "type": "string"
          },
          "client_phone": {
            "type": "string"
          },
          "crypto_deposit_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "network": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": "string"
          },
          "timestamp": {
            "type": "string"
          }
        },
        "required": [
          "event",
          "exchange_id",
          "external_id",
          "direction",
          "status",
          "previous_status",
          "amount_usdt",
          "amount_usdt_received",
          "amount_rub",
          "rate",
          "client_phone",
          "crypto_deposit_address",
          "network",
          "expires_at",
          "timestamp"
        ],
        "additionalProperties": false
      },
      "SubscriptionPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "const": "subscription.status_changed"
          },
          "subscription_id": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "past_due": {
            "type": "boolean"
          },
          "rebind_required": {
            "type": "boolean"
          },
          "binding_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "binding_url_pending": {
            "type": "boolean"
          },
          "previous_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "timestamp": {
            "type": "string"
          },
          "sandbox": {
            "type": "boolean"
          }
        },
        "required": [
          "event",
          "subscription_id",
          "external_id",
          "status",
          "past_due",
          "binding_url",
          "binding_url_pending",
          "previous_status",
          "timestamp"
        ],
        "additionalProperties": false
      },
      "SubscriptionChargeExhaustedPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "const": "subscription.charge_exhausted"
          },
          "subscription_id": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_sandbox": {
            "type": "boolean"
          },
          "period_ref": {
            "type": "string"
          },
          "retries_used": {
            "type": "integer"
          },
          "retries_max": {
            "type": "integer"
          },
          "retry_window_hours": {
            "type": "integer"
          },
          "next_allowed_at": {
            "type": "string"
          },
          "timestamp": {
            "type": "string"
          }
        },
        "required": [
          "event",
          "subscription_id",
          "external_id",
          "is_sandbox",
          "period_ref",
          "retries_used",
          "retries_max",
          "retry_window_hours",
          "next_allowed_at",
          "timestamp"
        ],
        "additionalProperties": false
      },
      "RefundPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "refund.completed",
              "refund.rejected",
              "refund.failed",
              "refund.reversed"
            ]
          },
          "payment_id": {
            "type": "string"
          },
          "refund_id": {
            "type": "string"
          },
          "amount_rub": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "anyOf": [
              {
                "type": "object",
                "additionalProperties": {}
              },
              {
                "type": "null"
              }
            ]
          },
          "shop_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "shop_name": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "event",
          "payment_id",
          "refund_id",
          "amount_rub",
          "status"
        ],
        "additionalProperties": false
      },
      "KycPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "const": "kyc.status_changed"
          },
          "client_id": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "verified",
              "failed"
            ]
          },
          "previous_status": {
            "type": "string",
            "enum": [
              "pending",
              "failed"
            ]
          },
          "verified_at": {
            "type": "string"
          },
          "timestamp": {
            "type": "string"
          }
        },
        "required": [
          "event",
          "client_id",
          "phone",
          "status",
          "previous_status",
          "verified_at",
          "timestamp"
        ],
        "additionalProperties": false
      },
      "GatewayPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "const": "gateway.status_changed"
          },
          "status": {
            "type": "string",
            "enum": [
              "operational",
              "degraded",
              "down"
            ]
          },
          "error_rate_1h": {
            "type": "number"
          },
          "completion_rate_1h": {
            "type": [
              "number",
              "null"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "api",
              "completion",
              "both"
            ]
          },
          "since": {
            "type": "string"
          },
          "timestamp": {
            "type": "string"
          }
        },
        "required": [
          "event",
          "status",
          "error_rate_1h",
          "completion_rate_1h",
          "reason",
          "since",
          "timestamp"
        ],
        "additionalProperties": false
      }
    }
  }
}
