{
  "openapi": "3.0.3",
  "info": {
    "title": "Sethara Pay Payment API",
    "description": "Integration guide and API reference for Sethara Pay. Start with \"Getting started\", then create a payment and handle callbacks.",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.setharapay.io",
      "description": "Production"
    },
    {
      "url": "https://beta.setharapay.io",
      "description": "Sandbox"
    }
  ],
  "paths": {
    "/intake": {
      "post": {
        "summary": "Create Pay-In",
        "operationId": "createPayIn",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePaymentResponse"
                }
              }
            }
          },
          "402": {
            "description": "SE_SHORT_OF_FUNDS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "SE_ACCOUNT_BARRED, SE_REGION_CLOSED",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "SE_NO_SUCH_BOOKING",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "SE_TRACE_KEY_SEEN, SE_ALREADY_CLEARED, SE_STATE_MISMATCH",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "SE_SUM_OFF_SCALE, SE_CCY_NOT_OPEN, SE_LEG_UNKNOWN, SE_ROUTE_CLOSED, SE_SUM_BELOW_FLOOR, SE_SUM_ABOVE_CEILING, SE_FROM_ACCT_BAD, SE_FROM_LABEL_BAD, SE_BACK_URL_BAD, SE_PAGE_KIND_BAD, SE_LEG_KEYS_BAD, SE_ADJUSTMENT_BAD, SE_PING_URL_BAD, SE_PAYLOAD_BAD",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "423": {
            "description": "SE_BOOKING_HELD",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "SE_DAY_CAP_HIT, SE_TOO_MANY_BOOKINGS, SE_LEG_SATURATED",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "SE_LEDGER_FAULT, SE_TARIFF_FAULT, SE_CLEARING_FAULT, SE_EDGE_FAULT, SE_BOOK_TIMEOUT, SE_UNMAPPED_FAULT",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "SE_LEG_SILENT",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "SE_LEG_ASLEEP, SE_LEDGER_BUSY",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/release": {
      "post": {
        "summary": "Create Pay-Out",
        "operationId": "createPayOut",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePaymentResponse"
                }
              }
            }
          },
          "402": {
            "description": "SE_SHORT_OF_FUNDS",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "SE_ACCOUNT_BARRED, SE_REGION_CLOSED",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "SE_NO_SUCH_BOOKING",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "SE_TRACE_KEY_SEEN, SE_ALREADY_CLEARED, SE_STATE_MISMATCH",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "SE_SUM_OFF_SCALE, SE_CCY_NOT_OPEN, SE_LEG_UNKNOWN, SE_ROUTE_CLOSED, SE_SUM_BELOW_FLOOR, SE_SUM_ABOVE_CEILING, SE_FROM_ACCT_BAD, SE_FROM_LABEL_BAD, SE_BACK_URL_BAD, SE_PAGE_KIND_BAD, SE_LEG_KEYS_BAD, SE_ADJUSTMENT_BAD, SE_PING_URL_BAD, SE_PAYLOAD_BAD",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "423": {
            "description": "SE_BOOKING_HELD",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "SE_DAY_CAP_HIT, SE_TOO_MANY_BOOKINGS, SE_LEG_SATURATED",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "SE_LEDGER_FAULT, SE_TARIFF_FAULT, SE_CLEARING_FAULT, SE_EDGE_FAULT, SE_BOOK_TIMEOUT, SE_UNMAPPED_FAULT",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "SE_LEG_SILENT",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "SE_LEG_ASLEEP, SE_LEDGER_BUSY",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/{id}/status": {
      "get": {
        "summary": "Check Payment Status",
        "operationId": "checkStatus",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payment status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePaymentResponse"
                }
              }
            }
          }
        }
      }
    },
    "/confirm-leg": {
      "post": {
        "summary": "Confirm Payment",
        "operationId": "confirmPayment",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Payment ID"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmation accepted"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CreatePaymentRequest": {
        "type": "object",
        "properties": {
          "book_sum": {
            "type": "string"
          },
          "book_ccy": {
            "type": "string"
          },
          "trace_key": {
            "type": "string"
          },
          "ping_url": {
            "type": "string"
          },
          "leg_type": {
            "type": "string",
            "enum": [
              "p2p_leg",
              "card_leg",
              "chain_leg"
            ]
          },
          "leg_route": {
            "type": "string"
          },
          "from_acct": {
            "type": "string"
          },
          "from_label": {
            "type": "string"
          },
          "back_url": {
            "type": "string"
          },
          "quiet_mode": {
            "type": "boolean"
          },
          "page_kind": {
            "type": "string"
          },
          "leg_keys": {
            "type": "string"
          }
        }
      },
      "CreatePaymentResponse": {
        "type": "object",
        "properties": {
          "book_id": {
            "type": "string"
          },
          "page_url": {
            "type": "string"
          },
          "book_state": {
            "type": "string",
            "enum": [
              "booked",
              "in_clearing",
              "settled",
              "declined",
              "voided",
              "recalled"
            ],
            "description": "Payment status"
          },
          "book_sum": {
            "type": "string"
          },
          "clear_sum": {
            "type": "string"
          },
          "book_ccy": {
            "type": "string"
          },
          "booked_at": {
            "type": "string"
          },
          "clear_at": {
            "type": "string"
          },
          "leg_keys": {
            "type": "string"
          },
          "halt_note": {
            "type": "string"
          },
          "stale_at": {
            "type": "string"
          },
          "trace_key": {
            "type": "string"
          }
        }
      },
      "StatusEnum": {
        "type": "string",
        "enum": [
          "booked",
          "in_clearing",
          "settled",
          "declined",
          "voided",
          "recalled"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "Error402": {
        "description": "SE_SHORT_OF_FUNDS: The payer does not hold enough balance to book this movement.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_SHORT_OF_FUNDS",
              "message": "The payer does not hold enough balance to book this movement."
            }
          }
        }
      },
      "Error404": {
        "description": "SE_NO_SUCH_BOOKING: No movement is booked under the reference you sent.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_NO_SUCH_BOOKING",
              "message": "No movement is booked under the reference you sent."
            }
          }
        }
      },
      "Error423": {
        "description": "SE_BOOKING_HELD: This movement is held while an adjustment clears.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_BOOKING_HELD",
              "message": "This movement is held while an adjustment clears."
            }
          }
        }
      },
      "Error455": {
        "description": "SE_SUM_OFF_SCALE: The amount sits outside the band allowed on this leg.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_SUM_OFF_SCALE",
              "message": "The amount sits outside the band allowed on this leg."
            }
          }
        }
      },
      "Error457": {
        "description": "SE_TRACE_KEY_SEEN: A movement already exists for this trace key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_TRACE_KEY_SEEN",
              "message": "A movement already exists for this trace key."
            }
          }
        }
      },
      "Error458": {
        "description": "SE_CCY_NOT_OPEN: This account clears in INR only.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_CCY_NOT_OPEN",
              "message": "This account clears in INR only."
            }
          }
        }
      },
      "Error459": {
        "description": "SE_LEG_SILENT: The upstream leg returned nothing; retry in a moment.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_LEG_SILENT",
              "message": "The upstream leg returned nothing; retry in a moment."
            }
          }
        }
      },
      "Error460": {
        "description": "SE_LEG_UNKNOWN: The requested leg is not wired for this account.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_LEG_UNKNOWN",
              "message": "The requested leg is not wired for this account."
            }
          }
        }
      },
      "Error461": {
        "description": "SE_LEG_ASLEEP: The leg is temporarily out of service.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_LEG_ASLEEP",
              "message": "The leg is temporarily out of service."
            }
          }
        }
      },
      "Error462": {
        "description": "SE_ROUTE_CLOSED: No open route matches this movement.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_ROUTE_CLOSED",
              "message": "No open route matches this movement."
            }
          }
        }
      },
      "Error464": {
        "description": "SE_ACCOUNT_BARRED: This merchant account is barred from booking movements.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_ACCOUNT_BARRED",
              "message": "This merchant account is barred from booking movements."
            }
          }
        }
      },
      "Error465": {
        "description": "SE_SUM_BELOW_FLOOR: The amount is under the floor set for this leg.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_SUM_BELOW_FLOOR",
              "message": "The amount is under the floor set for this leg."
            }
          }
        }
      },
      "Error466": {
        "description": "SE_SUM_ABOVE_CEILING: The amount is over the ceiling set for this leg.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_SUM_ABOVE_CEILING",
              "message": "The amount is over the ceiling set for this leg."
            }
          }
        }
      },
      "Error467": {
        "description": "SE_LEDGER_FAULT: The clearing ledger could not record this movement.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_LEDGER_FAULT",
              "message": "The clearing ledger could not record this movement."
            }
          }
        }
      },
      "Error468": {
        "description": "SE_LEDGER_BUSY: The clearing ledger is busy; retry shortly.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_LEDGER_BUSY",
              "message": "The clearing ledger is busy; retry shortly."
            }
          }
        }
      },
      "Error469": {
        "description": "SE_TARIFF_FAULT: The tariff for this movement could not be resolved.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_TARIFF_FAULT",
              "message": "The tariff for this movement could not be resolved."
            }
          }
        }
      },
      "Error470": {
        "description": "SE_ALREADY_CLEARED: This movement has already cleared and cannot change.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_ALREADY_CLEARED",
              "message": "This movement has already cleared and cannot change."
            }
          }
        }
      },
      "Error471": {
        "description": "SE_DAY_CAP_HIT: The daily booking cap for this account is reached.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_DAY_CAP_HIT",
              "message": "The daily booking cap for this account is reached."
            }
          }
        }
      },
      "Error472": {
        "description": "SE_REGION_CLOSED: Movements from this region are not accepted on this account.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_REGION_CLOSED",
              "message": "Movements from this region are not accepted on this account."
            }
          }
        }
      },
      "Error473": {
        "description": "SE_TOO_MANY_BOOKINGS: Too many movements booked in a short window; slow down.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_TOO_MANY_BOOKINGS",
              "message": "Too many movements booked in a short window; slow down."
            }
          }
        }
      },
      "Error474": {
        "description": "SE_FROM_ACCT_BAD: The counterparty account failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_FROM_ACCT_BAD",
              "message": "The counterparty account failed validation."
            }
          }
        }
      },
      "Error475": {
        "description": "SE_FROM_LABEL_BAD: The counterparty name failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_FROM_LABEL_BAD",
              "message": "The counterparty name failed validation."
            }
          }
        }
      },
      "Error476": {
        "description": "SE_BACK_URL_BAD: The return address is not a valid https URL.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_BACK_URL_BAD",
              "message": "The return address is not a valid https URL."
            }
          }
        }
      },
      "Error477": {
        "description": "SE_LEG_SATURATED: The leg is at capacity; try again in a minute.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_LEG_SATURATED",
              "message": "The leg is at capacity; try again in a minute."
            }
          }
        }
      },
      "Error478": {
        "description": "SE_PAGE_KIND_BAD: The requested payment page variant does not exist.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_PAGE_KIND_BAD",
              "message": "The requested payment page variant does not exist."
            }
          }
        }
      },
      "Error479": {
        "description": "SE_STATE_MISMATCH: The movement is not in a state that allows this call.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_STATE_MISMATCH",
              "message": "The movement is not in a state that allows this call."
            }
          }
        }
      },
      "Error480": {
        "description": "SE_CLEARING_FAULT: Clearing failed on our side; the movement was not booked.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_CLEARING_FAULT",
              "message": "Clearing failed on our side; the movement was not booked."
            }
          }
        }
      },
      "Error481": {
        "description": "SE_LEG_KEYS_BAD: The leg credentials supplied are incomplete or wrong.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_LEG_KEYS_BAD",
              "message": "The leg credentials supplied are incomplete or wrong."
            }
          }
        }
      },
      "Error482": {
        "description": "SE_ADJUSTMENT_BAD: The adjustment requested is not valid for this movement.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_ADJUSTMENT_BAD",
              "message": "The adjustment requested is not valid for this movement."
            }
          }
        }
      },
      "Error489": {
        "description": "SE_PING_URL_BAD: The webhook address is missing, private or not https.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_PING_URL_BAD",
              "message": "The webhook address is missing, private or not https."
            }
          }
        }
      },
      "Error527": {
        "description": "SE_EDGE_FAULT: The clearing edge could not be reached.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_EDGE_FAULT",
              "message": "The clearing edge could not be reached."
            }
          }
        }
      },
      "Error552": {
        "description": "SE_BOOK_TIMEOUT: Booking timed out before the leg answered.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_BOOK_TIMEOUT",
              "message": "Booking timed out before the leg answered."
            }
          }
        }
      },
      "Error568": {
        "description": "SE_PAYLOAD_BAD: The request body did not pass validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_PAYLOAD_BAD",
              "message": "The request body did not pass validation."
            }
          }
        }
      },
      "Error591": {
        "description": "SE_UNMAPPED_FAULT: The upstream leg failed in a way we do not recognise.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": "SE_UNMAPPED_FAULT",
              "message": "The upstream leg failed in a way we do not recognise."
            }
          }
        }
      }
    }
  },
  "x-topics": [
    {
      "title": "Getting started",
      "content": "## Environments\n\n| | Base URL |\n|---|---|\n| Sandbox | `https://beta.setharapay.io` |\n| Production | `https://api.setharapay.io` |\n\nCreate API keys in the merchant cabinet (`https://admin.setharapay.io`, Settings). Every request carries the key in `Authorization: Bearer <key>` or `X-Api-Key: <key>`. Requests are accepted only from the IP addresses allow-listed for the key. Keys can be rotated in the cabinet at any time; the previous key keeps working until you revoke it.\n\nAll amounts are strings with two decimals in INR. Timestamps are ISO 8601 in UTC."
    },
    {
      "title": "Create a payment",
      "content": "Call `POST /intake` or `POST /release` with the order amount, your own order reference and the webhook URL that will receive status updates.\n\n```json\n{\n  \"book_sum\": \"1250.00\",\n  \"book_ccy\": \"INR\",\n  \"trace_key\": \"order-2026-000123\",\n  \"ping_url\": \"https://merchant.example/hooks/payments\",\n  \"leg_type\": \"p2p_leg\"\n}\n```\n\n- `trace_key` makes the call idempotent: repeating a request with the same value returns the original payment instead of creating a second one. Use your order id.\n- `ping_url` is required. It must be an HTTPS URL reachable from the internet.\n- The response contains `book_id` (store it), `book_state` and `page_url`: redirect the payer to that page. It shows the payment details, opens the payer's UPI app and collects the confirmation. You do not need to render anything yourself."
    },
    {
      "title": "Payment lifecycle",
      "content": "A payment moves through the states below. Poll `GET /{id}/status` (no more often than every 5 seconds) or, better, rely on the callback and use polling only as a fallback.\n\n| State | Meaning | Next step |\n|---|---|---|\n| `booked` | awaiting the payer | intermediate, keep polling or wait for the callback |\n| `in_clearing` | the payer is completing the transfer; awaiting the payer | intermediate, keep polling or wait for the callback |\n| `settled` | awaiting the payer; funds received | intermediate, keep polling or wait for the callback |\n| `declined` | not completed; the payment window closed | final |\n| `voided` | not completed | final |\n| `recalled` | returned to the payer; awaiting the payer; disputed | intermediate, keep polling or wait for the callback |\n\nOnly final states are stable. Never treat an intermediate state as paid. `clear_sum` is the amount actually received and `clear_at` the time the funds were confirmed."
    },
    {
      "title": "Callbacks",
      "content": "Every state change is delivered with `POST` to the `ping_url` of the payment. The body has the same shape as the status response.\n\nEach delivery carries `X-Webhook-Signature: t=<unix seconds>,v1=<hex>`, where `v1` is HMAC-SHA256 of the string `<t>.<raw request body>` computed with the webhook secret issued to you at onboarding (keep it out of your client-side code). Recompute it over the raw body exactly as received, compare in constant time and reject deliveries whose `t` is older than five minutes. If no webhook secret was issued for your account, the header is absent and you must fetch the payment state with the status endpoint before acting on a callback.\n\nRespond with any 2xx status within 10 seconds. Any other response or a timeout is retried with increasing delays for about 32 hours, so your handler must be idempotent: the same event can arrive more than once. Process by `book_id` and the state, not by delivery order."
    },
    {
      "title": "Payer confirmation and receipts",
      "content": "On the hosted payment page the payer completes the transfer in a UPI app or by bank transfer and then confirms it either with the 12-digit bank reference (UTR) or by uploading a receipt. Sethara Pay matches the confirmation against the incoming funds; the payment stays in an intermediate state until the match is complete and then moves to a final state, which you receive through the callback.\n\nIf you collect the bank reference yourself, submit it with `POST /confirm-leg` together with `book_id`. A reference that does not match, was already used or belongs to a different amount ends the payment with a final failure state and `halt_note` explaining why; the payer is offered to upload a receipt or to contact support on the page."
    },
    {
      "title": "Errors",
      "content": "Errors are returned with an HTTP status and a stable code. Use the code, not the message, in your logic.\n\n| Code | HTTP | Message |\n|---|---|---|\n| `SE_SHORT_OF_FUNDS` | 402 | The payer does not hold enough balance to book this movement. |\n| `SE_NO_SUCH_BOOKING` | 404 | No movement is booked under the reference you sent. |\n| `SE_BOOKING_HELD` | 423 | This movement is held while an adjustment clears. |\n| `SE_SUM_OFF_SCALE` | 422 | The amount sits outside the band allowed on this leg. |\n| `SE_TRACE_KEY_SEEN` | 409 | A movement already exists for this trace key. |\n| `SE_CCY_NOT_OPEN` | 422 | This account clears in INR only. |\n| `SE_LEG_SILENT` | 502 | The upstream leg returned nothing; retry in a moment. |\n| `SE_LEG_UNKNOWN` | 422 | The requested leg is not wired for this account. |\n| `SE_LEG_ASLEEP` | 503 | The leg is temporarily out of service. |\n| `SE_ROUTE_CLOSED` | 422 | No open route matches this movement. |\n| `SE_ACCOUNT_BARRED` | 403 | This merchant account is barred from booking movements. |\n| `SE_SUM_BELOW_FLOOR` | 422 | The amount is under the floor set for this leg. |\n| `SE_SUM_ABOVE_CEILING` | 422 | The amount is over the ceiling set for this leg. |\n| `SE_LEDGER_FAULT` | 500 | The clearing ledger could not record this movement. |\n| `SE_LEDGER_BUSY` | 503 | The clearing ledger is busy; retry shortly. |\n| `SE_TARIFF_FAULT` | 500 | The tariff for this movement could not be resolved. |\n| `SE_ALREADY_CLEARED` | 409 | This movement has already cleared and cannot change. |\n| `SE_DAY_CAP_HIT` | 429 | The daily booking cap for this account is reached. |\n| `SE_REGION_CLOSED` | 403 | Movements from this region are not accepted on this account. |\n| `SE_TOO_MANY_BOOKINGS` | 429 | Too many movements booked in a short window; slow down. |\n| `SE_FROM_ACCT_BAD` | 422 | The counterparty account failed validation. |\n| `SE_FROM_LABEL_BAD` | 422 | The counterparty name failed validation. |\n| `SE_BACK_URL_BAD` | 422 | The return address is not a valid https URL. |\n| `SE_LEG_SATURATED` | 429 | The leg is at capacity; try again in a minute. |\n| `SE_PAGE_KIND_BAD` | 422 | The requested payment page variant does not exist. |\n| `SE_STATE_MISMATCH` | 409 | The movement is not in a state that allows this call. |\n| `SE_CLEARING_FAULT` | 500 | Clearing failed on our side; the movement was not booked. |\n| `SE_LEG_KEYS_BAD` | 422 | The leg credentials supplied are incomplete or wrong. |\n| `SE_ADJUSTMENT_BAD` | 422 | The adjustment requested is not valid for this movement. |\n| `SE_PING_URL_BAD` | 422 | The webhook address is missing, private or not https. |\n| `SE_EDGE_FAULT` | 500 | The clearing edge could not be reached. |\n| `SE_BOOK_TIMEOUT` | 500 | Booking timed out before the leg answered. |\n| `SE_PAYLOAD_BAD` | 422 | The request body did not pass validation. |\n| `SE_UNMAPPED_FAULT` | 500 | The upstream leg failed in a way we do not recognise. |\n| `SE_BOOKING_FAULT` | 500 | The movement could not be booked. Please retry or contact support. |\n\nValidation problems (missing fields, wrong types) come back as 400 with a list of fields. 401 means the key or the source IP is not accepted. 5xx responses are safe to retry with the same `trace_key`."
    },
    {
      "title": "Sandbox and testing",
      "content": "Use the sandbox base URL with sandbox keys from `https://beta-admin.setharapay.io`. Payments there never move real money: the payment page lets you complete or fail a payment on demand, so you can test every state, the callback signature and your retry handling. Check that your endpoint answers 2xx and that repeated deliveries do not create duplicate orders."
    },
    {
      "title": "Reconciliation",
      "content": "The merchant cabinet provides a settlement report (CSV or XLSX) for any period with the bank reference of every payment, gross amount, fees and net amount. Match the report against your bank statement by the bank reference. Payouts show the same breakdown per settlement."
    }
  ]
}
