kuti-pe
SDK oficial de KUTI para Python. Crea sesiones de checkout, consulta el estado de un pago y verifica webhooks — sin reimplementar auth, manejo de errores ni firma HMAC a mano.
Solo servidor. Este paquete usa tu secret key (
kuti_live_.../kuti_test_...). Nunca lo importes en código que se sirva al navegador.
Instalación
pip install kuti-pe
Quickstart
import os
from kuti import KutiClient
kuti = KutiClient(os.environ["KUTI_SECRET_KEY"])
session = kuti.checkout_sessions.create(
amount={"amount": "249.90", "currency": "PEN"},
payment_method_types=["INTEROPERABLE_QR", "BANK_TRANSFER"],
description="Zapatillas running talla 42",
customer={"id": "cus_01ABC"},
# customer={"first_name": "María", "last_name": "López", "email": "maria@example.com"},
idempotency_key=f"order-{order_id}",
)
# window.Kuti.open({ checkoutUrl: session.checkout_url, onSuccess, onFailure })
Clientes y campos personalizados
El cliente tiene la misma forma en customers.create, en el customer de un cobro y en el de
una checkout session. custom_fields son los campos que el negocio definió en
Ajustes → Clientes → Campos (la key de cada campo):
from kuti import CustomerInput
customer = kuti.customers.create(
CustomerInput(
type="INDIVIDUAL",
first_name="María",
last_name="López",
document={"type": "DNI", "number": "45678912"}, # type opcional: se deduce del número
email="maria@example.com",
custom_fields={"grade": "quinto", "student_code": "2026-00781"},
)
)
# En un cobro: se reutiliza el cliente por id → external_id → documento, o se crea.
kuti.payment_intents.create(
amount={"amount": "250.00", "currency": "PEN"},
payment_method_types=["INTEROPERABLE_QR"],
description="Pensión marzo",
customer={"document": {"number": "45678912"}, "custom_fields": {"grade": "sexto"}},
idempotency_key="pension-2026-03-45678912",
)
# Editar: solo cambian las keys enviadas; None borra el valor.
kuti.customers.update(customer.id, custom_fields={"birth_date": None})
Confirmar un pago
intent = kuti.payment_intents.retrieve(payment_intent_id)
if intent.status == "SUCCEEDED":
# fulfill order
pass
Verificar un webhook
from flask import Flask, request
from kuti import verify_webhook_signature, KutiSignatureVerificationError
import os
app = Flask(__name__)
@app.post("/webhooks/kuti")
def kuti_webhook():
payload = request.get_data(as_text=True) # body CRUDO, sin json.loads antes
try:
verify_webhook_signature(
payload,
request.headers["X-Kuti-Signature"],
request.headers["X-Kuti-Timestamp"],
os.environ["KUTI_WEBHOOK_SECRET"],
)
except KutiSignatureVerificationError:
return "Invalid signature", 400
event = request.get_json(force=True)
# payment.succeeded, checkout.session.completed, …
return "", 200
Manejo de errores
Todas las excepciones de la API extienden KutiApiError (status, code, request_id, correlation_id, doc_url, details):
from kuti import KutiValidationError, KutiNotFoundError, KutiPermissionError, KutiApiError
try:
kuti.checkout_sessions.create(...)
except KutiValidationError as err:
print(err.details) # [ErrorDetail(field="amount.amount", ...)]
except KutiNotFoundError:
pass
except KutiPermissionError as err:
if err.is_insufficient_scope:
... # a la API key le falta el permiso de este endpoint (edítala en el panel o usa otra)
elif err.is_dashboard_only:
... # endpoint solo del panel de KUTI (p. ej. cambiar la cuenta bancaria)
except KutiApiError as err:
print(err.code, err.request_id) # úsalo al reportar un bug a soporte
diagnosis = kuti.diagnostics.get_request(err.request_id) # qué pasó con esa llamada
Los GET y los POST con idempotency_key se reintentan automáticamente en errores de red o 429/503. Un POST sin idempotency_key nunca se reintenta, para no duplicar un cobro.
Links de pago
Un enlace permanente que pagan muchas personas (curso, entrada, donación). Cada pago es un cobro
normal con payment_link_id.
link = kuti.payment_links.create(
title="Taller de Excel — sábado 10am",
template="COURSE",
pricing="FIXED",
amount="120.00",
payment_method_types=["INTEROPERABLE_QR", "BANK_TRANSFER"],
customer_field_ids=["cfd_…"], # [] = solo nombre, apellido y correo
button_label="Inscribirme",
success_message="¡Listo! Te esperamos el sábado.",
success_button_label="Unirme al grupo",
success_button_url="https://chat.whatsapp.com/…",
)
print(link.url) # https://pay.kuti.pe/l/taller-de-excel
# Quienes pagaron el link
paid = kuti.payment_intents.list(payment_link_id=link.id, status="SUCCEEDED", per_page="all")
Enviar el cobro al crearlo
kuti.payment_intents.create(
amount={"amount": "250.00", "currency": "PEN"},
payment_method_types=["INTEROPERABLE_QR"],
customer={"id": "cus_…"},
send_via=["EMAIL", "WHATSAPP"], # None = ["EMAIL"]; [] = no enviar
)
API
KutiClient(secret_key, base_url=None)kuti.customers.create(customer, metadata=None)/retrieve(id)/update(id, ...)/list(...)/delete(id)kuti.checkout_sessions.create(...)— Checkout.jskuti.payment_intents.create(...)— cobro directokuti.payment_intents.list(...)— filtrosstatus,q,customer_id,source(single | link | recurring),payment_link_idkuti.payment_intents.retrieve(id)kuti.payment_intents.cancel(id)kuti.payment_intents.send_whatsapp(id, ...)kuti.payment_links.create(...)/retrieve(id)/update(id, ...)/list(...)/activate(id)/deactivate(id)/check_slug(slug, except_id=None)kuti.payment_exceptions.list(...)/resolve(id, status=..., note=None)— pagos para revisarkuti.webhook_deliveries.retrieve(id)/retry(id)— cada intento con el status HTTP y lo que respondió tu servidorkuti.diagnostics.get_request(request_id)/list_by_correlation_id(id)/trace_payment_intent(id)— permisodiagnostics:readverify_webhook_signature(payload, signature_header, timestamp_header, secret, tolerance_seconds=300)
Requisitos
Python 3.9+ · sin dependencias de runtime.
Metadata
Release files for kuti-pe 1.0.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| kuti_pe-1.0.6.tar.gz | 19.1 kB | Without provenance | Details for kuti_pe-1.0.6.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| kuti_pe-1.0.6-py3-none-any.whl | Python 3 | none | any | Details for kuti_pe-1.0.6-py3-none-any.whl |
Total release size: 41.3 kB
Release files / kuti_pe-1.0.6.tar.gz
| Download URL | kuti_pe-1.0.6.tar.gz |
|---|---|
| Size | 19.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1da24195225c92cacb38646ec73bd3d7e336ae95cb1c56d0cc6acc4ddbc9bdbe
|
|
BLAKE2b-256 checksum How to use checksums |
053445bd113ab179b7b43b3cc0b9402f2a660817dff0503648f181b0a27ed4a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / kuti_pe-1.0.6-py3-none-any.whl
| Download URL | kuti_pe-1.0.6-py3-none-any.whl |
|---|---|
| Size | 22.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fcf1e764c13cba7eeeb1f470f077fa5229c227f4409f58eb7a2e9ff369229d59
|
|
BLAKE2b-256 checksum How to use checksums |
329899bc068e91b8101640776561a1ccf0b689227de3a31601f473b035da7dae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Security for kuti-pe 1.0.6
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.