En esta página
Inicio rápido
Crea un proyecto, prueba una factura y un webhook firmado en Sandbox, crea las credenciales de producción y acepta el primer pago real en la red principal.
Crea una cuenta
Entra en paymos.io con un enlace mágico por correo (o con Google o una passkey): no hay contraseña que configurar.
Crea un proyecto
Abre Panel → Proyectos → Nuevo proyecto, ponle nombre al proyecto y activa los pares de activo y red con los que vayan a pagar tus clientes.
Consigue tus claves de API de prueba
Ve a Panel → Desarrollador → Claves de API y genera un par de claves de prueba:
- —Payment Key (
pk_test_…) — sirve para crear facturas de prueba y leer su estado - —API Secret (
sk_test_…) — sirve para calcular la firma HMAC-SHA256 de la petición (mira el aviso de más abajo)
El API Secret nunca sale de tu servidor. No lo envíes en cabeceras, en cadenas de consulta ni en código de cliente. No lo subas nunca a git y, si se filtra, rótalo de inmediato.
Registra un endpoint de webhook de prueba
Publica en una URL HTTPS el manejador de webhooks de tu servidor. Después abre en Sandbox Panel → Desarrollador → Webhooks y crea el endpoint: indica la URL HTTPS, elige la categoría Invoice y tu proyecto. Copia a tu servidor el secreto whsec_test_…, que se muestra una sola vez; va aparte del par de claves de API y firma la entrega de los webhooks de prueba.
Crea una factura de prueba
Usa uno de los SDK oficiales de servidor que aparecen abajo. Serializa la petición, genera la firma HMAC exacta, coloca las cabeceras de marca temporal, reintenta de forma segura y devuelve una respuesta tipada. La firma manual se documenta aparte, en Autenticación.
API_KEY_ID="pk_live_xxxxxxxxxxxx"
API_SECRET="sk_live_xxxxxxxxxxxx"
BODY='{"project_id":"prj_xxxxxxxxxxxx","amount":"100.00","currency":"USD","external_order_id":"order-123","client_id":"customer-456"}'
TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')
SIGNATURE=$(printf '%s\n%s\n%s\n%s\n%s' "$TS" POST /v1/invoices '' "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$API_SECRET" -binary | base64)
curl -sS https://api.paymos.io/v1/invoices \
-H "Authorization: HMAC-SHA256 $API_KEY_ID:$SIGNATURE" \
-H "X-Request-Timestamp: $TS" \
-H "Content-Type: application/json" \
-d "$BODY"
import { Paymos, externalOrderId } from '@paymos/sdk';
const paymos = new Paymos({
apiKey: process.env.PAYMOS_API_KEY,
apiSecret: process.env.PAYMOS_API_SECRET,
});
const invoice = await paymos.invoices.create({
projectId: 'prj_xxxxxxxxxxxx',
amount: '100.00',
currency: 'USD',
externalOrderId: externalOrderId('order'),
clientId: 'customer-456',
});
console.log(invoice.invoiceId, invoice.paymentUrl);
import os
from paymos import Paymos
paymos = Paymos(
api_key=os.environ["PAYMOS_API_KEY"],
api_secret=os.environ["PAYMOS_API_SECRET"],
)
invoice = paymos.invoices.create(
project_id="prj_xxxxxxxxxxxx",
amount="100.00",
currency="USD",
external_order_id="order-123",
client_id="customer-456",
)
print(invoice["invoice_id"], invoice["payment_url"])
<?php
use Paymos\Client;
use Paymos\ClientConfig;
use Paymos\IdempotencyKey;
$paymos = new Client(new ClientConfig(
getenv('PAYMOS_API_KEY'),
getenv('PAYMOS_API_SECRET')
));
$invoice = $paymos->invoices()->create(array(
'project_id' => 'prj_xxxxxxxxxxxx',
'amount' => '100.00',
'currency' => 'USD',
'external_order_id' => IdempotencyKey::externalOrderId('order'),
'client_id' => 'customer-456',
));
echo $invoice['invoice_id'] . ' ' . $invoice['payment_url'];
package main
import (
"context"
"fmt"
"os"
paymos "github.com/Paymos-labs/go-sdk/v2"
)
func main() {
client, err := paymos.NewClient(os.Getenv("PAYMOS_API_KEY"), os.Getenv("PAYMOS_API_SECRET"))
if err != nil {
panic(err)
}
invoice, err := client.Invoices.Create(context.Background(), paymos.CreateInvoiceParams{
ProjectID: "prj_xxxxxxxxxxxx",
Amount: "100.00",
Currency: "USD",
ExternalOrderID: "order-123",
ClientID: stringPointer("customer-456"),
})
if err != nil {
panic(err)
}
fmt.Println(invoice.InvoiceID, invoice.PaymentURL)
}
func stringPointer(value string) *string { return &value }
using Paymos;
using var paymos = new PaymosClient(
Environment.GetEnvironmentVariable("PAYMOS_API_KEY")!,
Environment.GetEnvironmentVariable("PAYMOS_API_SECRET")!);
var invoice = await paymos.Invoices.CreateAsync(new CreateInvoiceRequest(
ProjectId: "prj_xxxxxxxxxxxx",
Amount: "100.00",
Currency: "USD",
ExternalOrderId: "order-123",
ClientId: "customer-456"));
Console.WriteLine($"{invoice.InvoiceId} {invoice.PaymentUrl}");
import io.paymos.CreateInvoiceRequest;
import io.paymos.Invoice;
import io.paymos.PaymosClient;
PaymosClient paymos = new PaymosClient(
System.getenv("PAYMOS_API_KEY"),
System.getenv("PAYMOS_API_SECRET"));
Invoice invoice = paymos.invoices.create(
CreateInvoiceRequest.builder()
.projectId("prj_xxxxxxxxxxxx")
.amount("100.00")
.currency("USD")
.externalOrderId("order-123")
.clientId("customer-456")
.build());
System.out.println(invoice.invoiceId() + " " + invoice.paymentUrl());
require 'paymos'
paymos = Paymos::Client.new(
api_key: ENV.fetch('PAYMOS_API_KEY'),
api_secret: ENV.fetch('PAYMOS_API_SECRET')
)
invoice = paymos.invoices.create(
project_id: 'prj_xxxxxxxxxxxx',
amount: '100.00',
currency: 'USD',
external_order_id: 'order-123',
client_id: 'customer-456'
)
puts "#{invoice.invoice_id} #{invoice.payment_url}"
use paymos::{CreateInvoiceRequest, PaymosClient};
let paymos = PaymosClient::new(
std::env::var("PAYMOS_API_KEY")?,
std::env::var("PAYMOS_API_SECRET")?,
)?;
let invoice = paymos
.invoices()
.create(&CreateInvoiceRequest {
project_id: "prj_xxxxxxxxxxxx".to_owned(),
amount: "100.00".to_owned(),
currency: "USD".to_owned(),
external_order_id: "order-123".to_owned(),
network: None,
allow_multiple_payments: None,
customer_fee_percent: None,
client_id: Some("customer-456".to_owned()),
})
.await?;
println!("{} {}", invoice.invoice_id, invoice.payment_url);
Abre la página de pago
La respuesta incluye una payment_url. Redirige allí a tu cliente o integra esa misma URL con ?embed=true:
{
"invoice_id": "inv_5CcyDYmMUGtzYL10q0Iimr",
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"status": "awaiting_client",
"is_final": false,
"is_test": true,
"payment_url": "https://checkout.paymos.io/invoice/inv_5CcyDYmMUGtzYL10q0Iimr",
"order": {
"external_id": "order-123",
"client_id": "customer-456",
"amount": "100.00",
"currency": "USD"
},
"created_at": 1743594000,
"updated_at": 1743594000,
"expires_at": 1743597600
}
El cliente ve una página de pago con la dirección de la billetera, un código QR y una cuenta atrás. Puede pagar con cualquier activo y red que estén activados en el proyecto; consulta Monedas admitidas.
Si el canal del proyecto es Bot de Telegram, ese enlace lleva al bot de Paymos. Quien paga elige allí el activo y la red, recibe la dirección y el código QR y sigue el estado sin salir de Telegram, y sin cuenta en Paymos.
Para el modo redirección, el pago integrado y el manejo de eventos del navegador, mira Página de pago.
Procesa el webhook
Cuando el pago se confirma, Paymos envía un POST a la URL de webhook que tengas registrada:
{
"event_id": "evt_J7EEYeL9pZJukfj2c5OQ44",
"event_type": "invoice.paid",
"version": 1,
"occurred_at": 1739281200,
"data": {
"invoice_id": "inv_5CcyDYmMUGtzYL10q0Iimr",
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"status": "paid",
"is_final": true,
"is_test": false,
"payment_url": "https://checkout.paymos.io/invoice/inv_5CcyDYmMUGtzYL10q0Iimr",
"order": {
"external_id": "order-12345",
"client_id": "customer-67890",
"amount": "100.00",
"currency": "USD"
},
"payment": {
"currency": "USDT",
"network": "TRC20",
"chain_id": 728126428,
"contract_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"expected": "50.00",
"address": "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
"exchange_rate": "2.00",
"paid": "50.00",
"remaining": "0",
"fee": "0.50",
"net": "49.50",
"transfers": [
{
"tx_hash": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"amount": "50.00",
"status": "confirmed",
"created_at": 1739281020,
"confirmed_at": 1739281200,
"required_confirmations": 19,
"estimated_confirmation_at": 1739281080,
"explorer_url": "https://tronscan.org/#/transaction/abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
]
},
"expires_at": 1739284200,
"completed_at": 1739281200,
"created_at": 1739277600,
"updated_at": 1739281200
}
}
Verifica la cabecera X-Webhook-Signature y entrega el pedido.
No entregues nunca un pedido a partir de un evento del navegador (paymos:succeeded o un postMessage del iframe). Solo el webhook firmado del lado del servidor es fiable: las señales del navegador se pueden falsificar.
import { WebhookVerifier } from '@paymos/sdk';
const verifier = new WebhookVerifier(process.env.PAYMOS_WEBHOOK_SECRET);
// Keep the body as a Buffer until verification succeeds.
const event = verifier.constructEvent(signatureHeader, rawBody);
console.log(event.eventId, event.eventType, event.data);
import os
from paymos import WebhookVerifier
verifier = WebhookVerifier(os.environ["PAYMOS_WEBHOOK_SECRET"])
# Keep raw_body as bytes until verification succeeds.
event = verifier.construct_event(signature_header, raw_body)
print(event["event_id"], event["event_type"], event["data"])
<?php
use Paymos\Webhook\WebhookEvent;
use Paymos\Webhook\WebhookVerifier;
$verifier = new WebhookVerifier(getenv('PAYMOS_WEBHOOK_SECRET'));
// Keep $rawBody unchanged until verification succeeds.
$payload = $verifier->decodeVerifiedPayload($signatureHeader, $rawBody);
$event = new WebhookEvent($payload);
echo $event->id() . ' ' . $event->type();
package main
import (
"fmt"
"os"
"time"
paymos "github.com/Paymos-labs/go-sdk/v2"
)
type invoiceEventData struct {
InvoiceID string `json:"invoice_id"`
}
func handleWebhook(signatureHeader string, rawBody []byte) error {
verifier, err := paymos.NewWebhookVerifier(os.Getenv("PAYMOS_WEBHOOK_SECRET"), 5*time.Minute)
if err != nil {
return err
}
var event paymos.WebhookEvent[invoiceEventData]
if err := verifier.ConstructEvent(signatureHeader, rawBody, time.Now(), &event); err != nil {
return err
}
fmt.Println(event.EventID, event.EventType, event.Data.InvoiceID)
return nil
}
using Paymos;
var verifier = new WebhookVerifier(
Environment.GetEnvironmentVariable("PAYMOS_WEBHOOK_SECRET")!);
// Keep rawBody as ReadOnlySpan<byte> until verification succeeds.
var webhook = verifier.ConstructEvent<InvoiceEventData>(signatureHeader, rawBody);
Console.WriteLine($"{webhook.EventId} {webhook.EventType} {webhook.Data.InvoiceId}");
public sealed record InvoiceEventData(string InvoiceId);
import com.fasterxml.jackson.databind.JsonNode;
import io.paymos.WebhookEvent;
import io.paymos.WebhookVerifier;
import java.time.Instant;
WebhookVerifier verifier = new WebhookVerifier(System.getenv("PAYMOS_WEBHOOK_SECRET"));
// Keep rawBody as byte[] until verification succeeds.
WebhookEvent<JsonNode> event =
verifier.constructEvent(signatureHeader, rawBody, Instant.now());
System.out.println(event.eventId() + " " + event.eventType() + " " + event.data());
require 'paymos'
verifier = Paymos::WebhookVerifier.new(ENV.fetch('PAYMOS_WEBHOOK_SECRET'))
# Keep raw_body as the original String until verification succeeds.
event = verifier.construct_event(signature_header, raw_body)
puts "#{event.event_id} #{event.event_type} #{event.data}"
use paymos::{Invoice, WebhookEvent, WebhookVerifier};
let verifier = WebhookVerifier::new(std::env::var("PAYMOS_WEBHOOK_SECRET")?)?;
// Keep raw_body as &[u8] until verification succeeds.
let event: WebhookEvent<Invoice> = verifier.construct_event(signature_header, raw_body)?;
println!("{} {} {}", event.event_id, event.event_type, event.data.invoice_id);
Simula el pago de prueba
Marca la factura de prueba como pagada desde el simulador de pagos. No envíes activos reales a una factura de prueba. Comprueba que el endpoint registrado recibe el evento firmado y que tu servidor lo verifica con whsec_test_… antes de entregar el pedido.
Pasa a producción
Cuando la petición en Sandbox y el webhook firmado funcionen, cambia a producción en el panel. No hay ningún paso de aprobación manual. Cambiar de entorno no crea claves: abre Panel → Desarrollador → Claves de API y crea credenciales de producción aparte (pk_live_… y sk_live_…). Registra el endpoint de webhook de producción por separado y guarda su secreto whsec_live_… lejos del de prueba.
Acepta tu primer pago real en la red principal
Repite la petición de factura con las credenciales de producción, abre la payment_url que devuelve —la página de pago o el bot de Paymos, según el canal del proyecto— y envía un activo admitido por una de las redes principales que tengas activadas. Paymos detecta la transferencia y envía el webhook firmado de producción una vez alcanzadas las confirmaciones necesarias.
El recorrido habitual desde el registro hasta ese primer pago real lleva unos 10 minutos. Es una referencia de puesta en marcha, no un SLA: el tiempo de confirmación sigue dependiendo de la red, del importe y del estado de la cadena en ese momento.
Siguientes pasos
- —Claves de API — tipos de credencial, alcance y ciclo de vida
- —SDK de servidor — clientes oficiales para ocho ecosistemas de lenguaje
- —Página de pago — opciones de redirección y pago integrado
- —Pruebas — cómo probar en Sandbox
- —Webhooks — verificación de firma y política de reintentos