Documentación / Primeros pasos

Inicio rápido

Su primer pago en el sandbox en 15 minutos. Necesitará un servidor con Node.js, PHP, Python o Go; hay ejemplos para los cuatro.

Qué tendrá al finalSu web crea una factura, el cliente paga en la página de Coinetic (en el sandbox, con un botón) y su servidor recibe un webhook y entrega el pedido exactamente una vez.

1. Obtenga las claves del sandbox

  1. Regístrese en el panel del sandbox y active la 2FA.
  2. «Claves y webhooks» → «Crear clave»: guarde el key_id (ak_…) y el secreto.
  3. «Claves y webhooks» → «Añadir endpoint»: la URL de su gestor, por ejemplo https://shop.example/webhooks/coinetic (en el sandbox se admite http://localhost:3000/…). Guarde el secreto del webhook.

Los secretos se muestran una sola vez. Guárdelos en las variables de entorno del servidor:

.env (¡no lo suba al repositorio!)
API_KEY=ak_...
API_SECRET=...
WEBHOOK_SECRET=...

2. Descargue el cliente

El cliente es un único archivo sin dependencias externas: firma de solicitudes, creación y consulta de facturas y verificación de webhooks. Descárguelo para su lenguaje →

3. Cree una factura

El precio, como cadena, en USD, EUR, USDT o USDC. Derive el Idempotency-Key del número de pedido: un reintento tras un tiempo de espera agotado devolverá la misma factura.

create-invoice.js
import { CoineticClient } from "./cryptopay.js";

const cp = new CoineticClient({ baseUrl: "https://sandbox.coinetic.money", keyId: process.env.API_KEY, secret: process.env.API_SECRET });

const invoice = await cp.createInvoice({
  order_id: "ORDER-1001",                 // su número de pedido, único por comercio
  price_amount: "25.00",                  // cadena, no número
  price_currency: "USD",
  return_url: "https://shop.example/orders/ORDER-1001",
  metadata: { user_id: "42" },
}, "order-ORDER-1001");                   // Idempotency-Key

console.log(invoice.checkout_url);        // https://sandbox.coinetic.money/pay/inv_…#t=…
create-invoice.php
require 'Cryptopay.php';

$cp = new \Cryptopay\Client('https://sandbox.coinetic.money', getenv('API_KEY'), getenv('API_SECRET'));

$invoice = $cp->createInvoice([
    'order_id'       => 'ORDER-1001',
    'price_amount'   => '25.00',
    'price_currency' => 'USD',
    'return_url'     => 'https://shop.example/orders/ORDER-1001',
    'metadata'       => ['user_id' => '42'],
], 'order-ORDER-1001');

echo $invoice['checkout_url'];
create_invoice.py
import os
from cryptopay import CoineticClient

cp = CoineticClient("https://sandbox.coinetic.money", os.environ["API_KEY"], os.environ["API_SECRET"])

invoice = cp.create_invoice({
    "order_id": "ORDER-1001",
    "price_amount": "25.00",
    "price_currency": "USD",
    "return_url": "https://shop.example/orders/ORDER-1001",
    "metadata": {"user_id": "42"},
}, "order-ORDER-1001")

print(invoice["checkout_url"])
main.go
cp := &cryptopayclient.Client{
	BaseURL: "https://sandbox.coinetic.money",
	KeyID:   os.Getenv("API_KEY"),
	Secret:  os.Getenv("API_SECRET"),
	HTTP:    &http.Client{Timeout: 10 * time.Second},
}
inv, err := cp.CreateInvoice(ctx, cryptopayclient.CreateInvoice{
	OrderID:       "ORDER-1001",
	PriceAmount:   "25.00",
	PriceCurrency: "USD",
	ReturnURL:     "https://shop.example/orders/ORDER-1001",
	Metadata:      map[string]string{"user_id": "42"},
}, "order-ORDER-1001")
if err != nil {
	return err
}
fmt.Println(inv.CheckoutURL)

4. Envíe al cliente a pagar

Redirija el navegador a invoice.checkout_url (HTTP 303). El cliente elegirá la moneda y verá la dirección, el importe y el código QR. En el sandbox, debajo de los datos de pago, aparecen los botones de pago de prueba: «Pagar importe exacto», «Pago insuficiente 10 %», «Sobrepago 10 %», «Mitad del importe» y «Pagar el resto».

No la inserte en un iframeLa página de pago prohíbe que se inserte en otras páginas para impedir la suplantación de la dirección. Ábrala en la misma pestaña o en el navegador del sistema de la app.

5. Reciba el webhook

El orden es siempre el mismo: verificar la firma → descartar repeticiones → volver a consultar el estado en la API → entregar el pedido una sola vez → responder 2xx.

webhook.js
import express from "express";
import { verifyWebhook } from "./cryptopay.js";
// cp es el cliente del paso 3. alreadyProcessed / markProcessed / fulfil son funciones suyas.

const app = express();

// express.raw: la firma se calcula sobre los bytes sin procesar; no use express.json() en esta ruta
app.post("/webhooks/coinetic", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = verifyWebhook(process.env.WEBHOOK_SECRET, req.headers, req.body);
  } catch (e) {
    return res.status(e.status ?? 400).end();               // 401: firma no válida; 400: timestamp antiguo
  }
  try {
    const eventId = req.headers["x-webhook-id"];
    if (await alreadyProcessed(eventId)) return res.sendStatus(200);
    const inv = await cp.getInvoice(event.data.invoice_id); // el estado, de la API, no del webhook
    if (inv.status === "paid" || inv.status === "overpaid") await fulfil(inv.order_id);
    await markProcessed(eventId);
    res.sendStatus(200);
  } catch (e) {
    console.error(e);
    res.sendStatus(500);                                    // si no es 2xx, Coinetic reintentará la entrega
  }
});

app.listen(3000);
webhook.php
require 'Cryptopay.php';

$headers = array_change_key_case(getallheaders(), CASE_LOWER);
$raw = file_get_contents('php://input');                   // cuerpo sin procesar, antes de json_decode
try {
    $event = \Cryptopay\verify_webhook(getenv('WEBHOOK_SECRET'), $headers, $raw);
} catch (\Cryptopay\CryptopayException $e) {
    http_response_code($e->status);
    exit;
}
if (already_processed($headers['x-webhook-id'])) { http_response_code(200); exit; }

$inv = $cp->getInvoice($event['data']['invoice_id']);       // el estado, de la API
if (in_array($inv['status'], \Cryptopay\PAID_STATUSES, true)) {
    fulfil($inv['order_id']);
}
mark_processed($headers['x-webhook-id']);
http_response_code(200);
webhook.py
import os
from flask import Flask, request
from cryptopay import verify_webhook, CryptopayError, PAID_STATUSES

app = Flask(__name__)

@app.post("/webhooks/coinetic")
def webhook():
    headers = {k.lower(): v for k, v in request.headers.items()}
    try:
        event = verify_webhook(os.environ["WEBHOOK_SECRET"], headers, request.get_data())
    except CryptopayError as e:
        return "", e.status
    if already_processed(headers["x-webhook-id"]):
        return "", 200
    inv = cp.get_invoice(event["data"]["invoice_id"])      # el estado, de la API
    if inv["status"] in PAID_STATUSES:
        fulfil(inv["order_id"])
    mark_processed(headers["x-webhook-id"])
    return "", 200
webhook.go
func webhook(w http.ResponseWriter, r *http.Request) {
	raw, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	if err := cryptopayclient.VerifyWebhook(os.Getenv("WEBHOOK_SECRET"), r.Header, raw, time.Now(), 5*time.Minute); err != nil {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}
	var ev struct {
		Data struct{ InvoiceID string `json:"invoice_id"` } `json:"data"`
	}
	if err := json.Unmarshal(raw, &ev); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	inv, err := cp.GetInvoice(r.Context(), ev.Data.InvoiceID) // el estado, de la API
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError) // Coinetic reintentará
		return
	}
	if cryptopayclient.PaidStatuses[inv.Status] {
		fulfilOnce(inv.OrderID)
	}
	w.WriteHeader(http.StatusOK)
}

6. Devuelva al cliente a su web

Tras el pago, en la página aparece el botón «Volver a la tienda», que lleva a su return_url. Muestre ahí el estado del pedido según su propia base de datos. Si el webhook aún no ha llegado, vuelva a consultar GET /v1/invoices/{id}.

Próximos pasos