# Recibir pagos con criptomonedas en Python

> Recibe Bitcoin y USDT en una app Python: firma solicitudes a la API con hmac, crea un pago con requests, redirige a la página de pago y verifica el webhook en Flask.

Source: https://cryptolike.net/es/guides/accept-crypto-payments-python
Published: 2026-09-25 · Updated: 2026-10-04 · Author: CryptoLike team

Una aplicación Python recibe cripto con CryptoLike en tres pasos: firma cada solicitud a la API con los módulos estándar `hmac` y `hashlib`, crea un pago para el pedido y verifica el webhook firmado antes de marcar el pedido como pagado. Los ejemplos usan `requests` y Flask; la misma lógica sirve para Django, FastAPI y otros frameworks.

Una tienda en WooCommerce, OpenCart, PrestaShop o Magento 2 no necesita código: revisa las [integraciones](https://cryptolike.net/integrations). Esta guía es para tu propio backend.

## Qué necesitas

Una cuenta de CryptoLike, Python 3.10 o superior y una URL pública `https://` para los webhooks. Guarda los secretos en el entorno: los ejemplos leen `CRYPTOLIKE_API_KEY_ID`, `CRYPTOLIKE_API_SECRET` y `CRYPTOLIKE_WEBHOOK_SECRET`, además de `CRYPTOLIKE_API_URL` para la URL de la API de prueba (si no está definida, se usa producción). Empieza con claves de prueba y redes de prueba.

## 1. Crea una clave API de prueba

En el panel abre **Desarrolladores → Claves API → Crear clave** con los permisos `create_invoice` y `read`. Una clave `ck_test_` funciona con redes de prueba, donde las monedas no tienen valor. Las claves de prueba se crean en el entorno de prueba: el soporte de CryptoLike te da acceso a su panel, donde creas la misma clave, y la URL de su API (`CRYPTOLIKE_API_URL` en los ejemplos). El panel de producción emite solo claves reales, y en la red principal una clave de prueba se rechaza con `401 auth.test_key`. El secreto se muestra una sola vez; ponlo de inmediato en tu entorno o en tu gestor de secretos.

## 2. Firma las solicitudes a la API con hmac

La firma es un HMAC-SHA256 de cuatro líneas —la marca de tiempo Unix en segundos, el método, la ruta con su query y el SHA-256 del cuerpo— con el secreto de la API como clave:

```python
import hashlib, hmac, json, os, time
import requests

def cryptolike(method: str, path: str, payload: dict | None = None, idempotency_key: str | None = None) -> dict:
    body = b"" if payload is None else json.dumps(payload, separators=(",", ":")).encode()
    ts = str(int(time.time()))
    canonical = "\n".join([ts, method.upper(), path, hashlib.sha256(body).hexdigest()])
    headers = {
        "X-API-Key": os.environ["CRYPTOLIKE_API_KEY_ID"],
        "X-Timestamp": ts,
        "X-Signature": hmac.new(os.environ["CRYPTOLIKE_API_SECRET"].encode(), canonical.encode(), hashlib.sha256).hexdigest(),
        "Content-Type": "application/json",
    }
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key
    r = requests.request(method, os.environ.get("CRYPTOLIKE_API_URL", "https://api.cryptolike.net") + path, data=body or None, headers=headers, timeout=30)
    data = r.json()
    if r.status_code >= 400:
        raise RuntimeError(f"{data.get('error', {}).get('code', r.status_code)}: {data.get('error', {}).get('message', '')}")
    return data
```

Envía los mismos bytes de los que calculaste el hash: pasa `data=body`, no `json=payload`, que volvería a serializar el cuerpo. Compara la función con el vector de referencia de [autenticación de la API](https://cryptolike.net/docs/auth).

## 3. Crea el pago de un pedido

Crea el pago en el servidor cuando se hace el pedido. Ponle precio en USD o EUR y deja que el cliente elija la moneda cripto, o pasa un `coin_id` de `GET /v1/currencies`. Sin `coin_id`, la página muestra las monedas disponibles para facturas, cada una con un monto aproximado marcado con ≈; agrega `allowed_coin_ids` (id con `invoice_enabled: true` en `GET /v1/currencies` firmado) para ofrecer solo algunas. Con el ID del pedido como clave de idempotencia, un reintento devuelve el mismo pago:

```python
invoice = cryptolike("POST", "/v1/invoices", {
    "amount": "49.90",
    "currency": "USD",
    "order_id": order.id,
    "idempotency_by_order_id": True,
    "return_url": f"https://example.com/orders/{order.id}",
}, idempotency_key=f"order-{order.id}")

order.invoice_id = invoice["id"]
```

Mantén los montos como cadenas o `Decimal`, nunca `float`.

## 4. Envía al cliente a la página de pago

Redirige a `invoice["checkout_url"]` (`redirect(invoice["checkout_url"], code=303)` en Flask). La página muestra el monto, la red, la dirección con código QR, el tiempo restante y el estado en vivo, y luego devuelve al cliente a `return_url`. Para que el cliente se quede en tu sitio, incrusta la página con el [widget de pago](https://cryptolike.net/docs/widget).

## 5. Verifica el webhook en Flask

Agrega un endpoint de webhook en **Desarrolladores → Webhooks** con los eventos `invoice.*`. Verifica sobre el cuerpo sin procesar de `request.get_data()`, descarta duplicados por `event_id` y responde rápido:

```python
from flask import Flask, request

app = Flask(__name__)

@app.post("/webhooks/cryptolike")
def cryptolike_webhook():
    raw = request.get_data()
    ts = request.headers.get("X-Timestamp", "")
    got = request.headers.get("X-Signature", "")
    secret = os.environ.get("CRYPTOLIKE_WEBHOOK_SECRET", "")
    want = "sha256=" + hmac.new(secret.encode(), ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
    if not secret or not ts.isdigit() or abs(time.time() - int(ts)) > 300 or not hmac.compare_digest(got, want):
        return "", 401
    event = json.loads(raw)
    if first_time(event["event_id"]) and event["type"] == "invoice.paid":
        mark_order_paid(event["data"]["invoice"]["order_id"])
    return "", 200
```

Cada evento se entrega al menos una vez y puede llegar en desorden: guíate por el estado dentro de `data`. El procedimiento completo está en [cómo verificar los webhooks de pagos cripto](https://cryptolike.net/guides/verify-crypto-payment-webhooks).

## 6. Prueba en una red de prueba y pasa a producción

Crea un pago con la clave de prueba y págalo con monedas de prueba en la red que indica la página: primero llega `invoice.pending` y después `invoice.paid`. Maneja también `invoice.underpaid`, `invoice.expired` e `invoice.reverted`; luego cambia a una clave real y sigue la lista de verificación para producción de la [guía para desarrolladores](https://cryptolike.net/docs).

## Errores comunes y cómo corregirlos

| Error | Qué pasó | Qué hacer |
|---|---|---|
| `401 auth.invalid_signature` | El cuerpo de la solicitud no coincide con lo que se firmó | Envía los mismos bytes que firmaste, con `data=` y no con `json=` |
| `401 auth.expired_timestamp` | El reloj tiene un desfase de más de 5 minutos | Sincroniza el reloj del servidor con NTP |
| `401` en tu webhook | El cuerpo se parseó o decodificó antes de la verificación | Verifica sobre `request.get_data()` |
| `400 validation_error` | Falta un campo o tiene un formato incorrecto | Lee `details.field`; envía los montos como cadenas |
| `409 conflict` | El pedido ya tiene un pago | Usa `details.invoice_id` en lugar de crear otro |
| `422 no_allowed_coins` | Ninguna moneda de `allowed_coin_ids` está disponible para facturas en esta divisa | Envía otros id o quita el campo para ofrecer todas las monedas |

## ¿Prefieres un SDK?

El [SDK oficial de Python](https://cryptolike.net/integrations/sdk-python) para Python 3.10+ envuelve estas llamadas: solicitudes firmadas, reintentos ante `429` y `5xx`, un `Idempotency-Key` en cada `POST`, paginación y verificación de webhooks. El código de arriba es lo que el SDK hace por dentro, así que puedes cambiar más adelante sin modificar el flujo.

## Preguntas frecuentes

### ¿Por qué falla la firma cuando uso el argumento json= de requests?

`json=payload` vuelve a serializar el cuerpo con sus propios espacios, así que los bytes enviados son distintos de los bytes de los que calculaste el hash. Serializa una sola vez, calcula el hash de esos bytes y pasa los mismos bytes con `data=`. Lo mismo aplica del lado del webhook: verifica sobre `request.get_data()` antes de parsear nada.

### ¿Puedo usar Django o FastAPI en lugar de Flask?

Sí. La lógica es la misma en todos los frameworks: lee el cuerpo sin procesar, calcula `sha256=` más el HMAC de la marca de tiempo, un punto y el cuerpo, compara con `hmac.compare_digest`, rechaza las marcas de tiempo que se alejen más de 300 segundos de tu reloj y después parsea. En Django el cuerpo sin procesar es `request.body`; en FastAPI, `await request.body()`.

### ¿Cómo pruebo sin gastar cripto real?

Usa una clave `ck_test_` con la URL de la API de prueba que te da el soporte de CryptoLike. Las claves de prueba funcionan con redes de prueba, donde las monedas de prueba no tienen valor, y todo el flujo —página de pago, confirmaciones, webhooks firmados— se comporta igual que en producción. Guarda las claves de prueba y las reales en configuraciones separadas para que nunca se mezclen.
