En esta página
Crear
Crea una identidad de cobro reutilizable para un cliente, con tu propio identificador externo y una dirección permanente en cada red admitida.
Clave de API: Payment
Un canal de pago es una identidad de cobro permanente para un cliente tuyo. A diferencia de una factura, no tiene importe, ni plazo, ni estado final: lo creas una vez, enseñas sus direcciones y cada transferencia que llega se convierte en un depósito abonado a tu saldo.
Cuerpo de la petición
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
project_id |
string | Sí | Identificador de proyecto con prefijo (prj_…). El proyecto decide qué tokens acepta el canal. |
external_id |
string | Sí | Tu propio identificador estable del cliente, de hasta 128 caracteres. Es inmutable una vez que el canal existe. |
{
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"external_id": "customer-42"
}
Idempotencia
La pareja project_id + external_id, dentro del entorno de la clave con la que firmas, es la clave de idempotencia. No hay nada más que enviar, así que una llamada repetida no deja ningún conflicto que resolver.
- —Primera llamada →
201 Created, con una cabeceraLocationque apunta al canal nuevo. - —Cualquier llamada posterior con la misma pareja →
200 OKcon ese mismo canal. No se crea ningún duplicado. - —El mismo
external_iden Sandbox y en producción son dos canales independientes.
Envía el identificador de cliente que ya usas en tu sistema y llama a este endpoint en cada cobro, en vez de guardarte el id pc_. Repetir la llamada no es un error ni crea un duplicado: los dos códigos devuelven un canal, y el 200 trae las direcciones y los tokens que el canal tiene hoy. Reintentar una petición que se quedó sin respuesta te devuelve el canal original en lugar de abrir uno nuevo.
Ejemplos de código
Cada pestaña usa el SDK oficial de ese lenguaje. La firma, la serialización, los reintentos seguros y los errores tipados los resuelve el SDK.
API_KEY_ID="pk_live_xxxxxxxxxxxx"
API_SECRET="sk_live_xxxxxxxxxxxx"
BODY='{"project_id":"prj_xxxxxxxxxxxx","external_id":"customer-42"}'
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/payment-channels '' "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$API_SECRET" -binary | base64)
curl -sS https://api.paymos.io/v1/payment-channels \
-H "Authorization: HMAC-SHA256 $API_KEY_ID:$SIGNATURE" \
-H "X-Request-Timestamp: $TS" \
-H "Content-Type: application/json" \
-d "$BODY"
import { Paymos } from '@paymos/sdk';
const paymos = new Paymos({
apiKey: process.env.PAYMOS_API_KEY,
apiSecret: process.env.PAYMOS_API_SECRET,
});
const channel = await paymos.paymentChannels.create({
projectId: 'prj_xxxxxxxxxxxx',
externalId: 'customer-42',
});
for (const rail of channel.networks) {
console.log(rail.network, rail.address ?? rail.status);
}
import os
from paymos import Paymos
paymos = Paymos(
api_key=os.environ["PAYMOS_API_KEY"],
api_secret=os.environ["PAYMOS_API_SECRET"],
)
channel = paymos.payment_channels.create(
project_id="prj_xxxxxxxxxxxx",
external_id="customer-42",
)
for rail in channel["networks"]:
print(rail["network"], rail.get("address", rail["status"]))
<?php
use Paymos\Client;
use Paymos\ClientConfig;
$paymos = new Client(new ClientConfig(
getenv('PAYMOS_API_KEY'),
getenv('PAYMOS_API_SECRET')
));
$channel = $paymos->paymentChannels()->create(array(
'project_id' => 'prj_xxxxxxxxxxxx',
'external_id' => 'customer-42',
));
foreach ($channel['networks'] as $rail) {
$address = isset($rail['address']) ? $rail['address'] : $rail['status'];
echo $rail['network'] . ' ' . $address . PHP_EOL;
}
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)
}
channel, err := client.PaymentChannels.Create(context.Background(), paymos.CreatePaymentChannelParams{
ProjectID: "prj_xxxxxxxxxxxx",
ExternalID: "customer-42",
})
if err != nil {
panic(err)
}
for _, rail := range channel.Networks {
fmt.Println(rail.Network, addressOrStatus(rail))
}
}
func addressOrStatus(rail paymos.PaymentChannelNetwork) string {
if rail.Address == nil {
return string(rail.Status)
}
return *rail.Address
}
using Paymos;
using var paymos = new PaymosClient(
Environment.GetEnvironmentVariable("PAYMOS_API_KEY")!,
Environment.GetEnvironmentVariable("PAYMOS_API_SECRET")!);
var channel = await paymos.PaymentChannels.CreateAsync(new CreatePaymentChannelRequest(
ProjectId: "prj_xxxxxxxxxxxx",
ExternalId: "customer-42"));
foreach (var rail in channel.Networks)
{
Console.WriteLine($"{rail.Network} {rail.Address ?? rail.Status}");
}
import io.paymos.CreatePaymentChannelRequest;
import io.paymos.PaymentChannel;
import io.paymos.PaymentChannelNetwork;
import io.paymos.PaymosClient;
PaymosClient paymos = new PaymosClient(
System.getenv("PAYMOS_API_KEY"),
System.getenv("PAYMOS_API_SECRET"));
PaymentChannel channel = paymos.paymentChannels.create(
new CreatePaymentChannelRequest("prj_xxxxxxxxxxxx", "customer-42"));
for (PaymentChannelNetwork rail : channel.networks()) {
System.out.println(rail.network() + " "
+ (rail.address() == null ? rail.status() : rail.address()));
}
require 'paymos'
paymos = Paymos::Client.new(
api_key: ENV.fetch('PAYMOS_API_KEY'),
api_secret: ENV.fetch('PAYMOS_API_SECRET')
)
channel = paymos.payment_channels.create(
project_id: 'prj_xxxxxxxxxxxx',
external_id: 'customer-42'
)
channel.networks.each do |rail|
puts "#{rail.network} #{rail.address || rail.status}"
end
use paymos::{CreatePaymentChannelRequest, PaymosClient};
let paymos = PaymosClient::new(
std::env::var("PAYMOS_API_KEY")?,
std::env::var("PAYMOS_API_SECRET")?,
)?;
let channel = paymos
.payment_channels()
.create(&CreatePaymentChannelRequest {
project_id: "prj_xxxxxxxxxxxx".to_owned(),
external_id: "customer-42".to_owned(),
})
.await?;
for rail in &channel.networks {
println!("{} {}", rail.network, rail.address.as_deref().unwrap_or(&rail.status));
}
Respuesta (201 Created / 200 si se repite la llamada)
{
"id": "pc_2QhKZv6mRt9WdA3nYpLbXf",
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"external_id": "customer-42",
"status": "active",
"is_accepting_payments": true,
"is_fully_provisioned": false,
"is_test": false,
"applied_fee_percent": 1.0,
"customer_fee_percent": 0,
"networks": [
{
"network": "TRC20",
"status": "active",
"address": "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
"tokens": [
{ "symbol": "USDT", "minimum_deposit": "1.00" },
{ "symbol": "USDC", "minimum_deposit": "1.00" }
]
},
{
"network": "ERC20",
"status": "active",
"address": "0x9f2a6c1b4e8d7350ab11c9e5f0d2743b86ce41d9",
"tokens": [
{ "symbol": "USDT", "minimum_deposit": "12.00" },
{ "symbol": "USDC", "minimum_deposit": "12.00" }
]
},
{
"network": "SOL",
"status": "provisioning",
"tokens": [
{ "symbol": "USDC", "minimum_deposit": null }
]
}
],
"created_at": 1767225600,
"updated_at": 1767225780
}
Redes y direcciones
Un canal no tiene lista de tokens propia. Acepta lo que acepte el proyecto: activar un token ahí lo añade a todos los canales de ese proyecto y desactivarlo lo quita de todos a la vez. El conjunto aceptado se gestiona en Panel → Proyectos.
Cada entrada de networks describe una cadena:
| Campo | Significado |
|---|---|
network |
Código de red, por ejemplo TRC20 o ERC20. |
status |
active en cuanto la dirección existe; provisioning hasta ese momento. |
address |
La dirección de depósito permanente. No viene hasta que esa red termina de aprovisionarse: es todavía no, no nunca. Una vez devuelta, ya no cambia. |
tokens |
Lo que esa red acepta ahora mismo. Cada entrada es un objeto con symbol y minimum_deposit, no un símbolo suelto. |
Dos reglas gobiernan lo que le enseñas a un cliente:
- —Presenta únicamente las redes
activecon un arraytokensno vacío. Una entrada enprovisioningtodavía no tiene dirección que enseñar. - —Deja de ofrecer una vía que la API ya no devuelve. Las direcciones nunca se reasignan, pero una red que quitaste del proyecto desaparece de la respuesta, y el dinero enviado ahí ya no se espera.
Una dirección, una vez devuelta, es de ese canal para siempre. Guárdala en caché: releer el canal devuelve el mismo valor, y esa misma dirección sirve para todos los tokens de esa red.
Cada entrada de token trae su propio minimum_deposit: la transferencia más pequeña que esa vía abona. Tómalo de la misma respuesta que estás mostrando y enséñalo junto a la dirección. Obtener canal explica el campo, incluido el caso null.
Aprovisionamiento
Un canal nuevo de producción nace en provisioning y sin direcciones. Cada red se aprovisiona por separado y queda utilizable en cuanto existe su propia dirección, sin esperar a la última cadena. El canal pasa a active con la primera dirección lista y ahí se queda.
is_fully_provisioned te dice si ya están listas todas las redes requeridas. Un canal puede estar active sin estar aprovisionado del todo: es el estado normal mientras las cadenas restantes se ponen al día.
Los canales de Sandbox están listos al instante. Sus direcciones se derivan en local y no tocan ninguna cadena.
Comisiones
applied_fee_percent y customer_fee_percent son tus tarifas actuales. Se muestran a título informativo y no son una cotización: cambian cuando cambia tu tarifa.
Los números que mandan viven en cada depósito. Todo depósito registra las tarifas vigentes en el instante en que se atribuyó e informa de su propio gross, fee y net. Un cambio de tarifa nunca reescribe un pago que ya recibiste. No existe ningún endpoint para previsualizar la comisión: crea un depósito en Sandbox si quieres ver la aritmética.
Errores
| Code | HTTP | Cuándo |
|---|---|---|
payment_channels_disabled |
503 | Los canales de pago no están activados para tu cuenta en este entorno. |
payment_key_required |
403 | La petición se firmó con una clave Payout (rk_). |
project_not_found |
404 | project_id no resuelve a nada visible para esta credencial. |
payment_channel_external_id_invalid |
400 | external_id está vacío, en blanco o supera los 128 caracteres. |
payment_channel_project_has_no_supported_tokens |
409 | El proyecto no activa ningún token que un canal pueda cobrar. |
Consulta Códigos de error para el catálogo completo.
Lo que esta API no tiene
No hay endpoint de actualización, de borrado, de reasignación, de URL de callback, ni de tokens o previsualización de comisión por canal. La identidad de un canal es inmutable por diseño: una dirección que un cliente ya guardó no puede pasar a manos de otro. Para dar de baja un canal, bloquéalo.