本页内容
快速开始
创建项目,在沙盒中跑通账单与带签名的 webhook,然后创建正式凭据,接收第一笔主网真实付款。
创建账户
在 paymos.io 用邮箱魔法链接(或 Google / passkey)登录——无需设置密码。
创建项目
打开 控制台 → 项目 → 新建项目,为项目命名,并开启客户将要使用的资产与网络组合。
获取测试 API 密钥
进入 控制台 → 开发者 → API 密钥,生成一对测试密钥:
- —Payment Key(
pk_test_…)——用于创建测试账单和查询账单状态 - —API Secret(
sk_test_…)——用于计算 HMAC-SHA256 请求签名(见下方警告)
API Secret 绝不离开你的服务器。不要放进请求头、查询字符串或客户端代码。不要提交到 git——一旦泄露立即轮换。
注册测试 webhook 端点
把服务器的 webhook 处理器发布到一个 HTTPS 地址,然后在沙盒中打开 控制台 → 开发者 → Webhook 创建端点:填入 HTTPS 回调地址,选择账单类目和你的项目。把只显示一次的 whsec_test_… 密钥复制到服务器;它与 API 密钥对相互独立,用于签名测试 webhook 的投递。
创建测试账单
使用下面的官方服务器 SDK。它负责序列化请求、生成精确的 HMAC 签名、设置时间戳请求头、执行安全重试,并返回类型化响应。手动签名另见 认证。
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);
打开收银台
响应中包含 payment_url。把客户重定向过去,或用 ?embed=true 嵌入同一 URL:
{
"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
}
客户会看到带钱包地址、QR 码和倒计时器的付款页。他们可以用项目已启用的任意资产 + 网络付款——见 支持的币种。
如果项目的渠道是 Telegram 机器人,这个链接会把客户带到 Paymos 机器人。付款人在那里选择资产和网络、拿到地址和二维码、查看状态变化,全程不离开 Telegram,也不需要 Paymos 账户。
重定向模式、iframe 嵌入模式和浏览器事件处理见 托管收银台。
处理 webhook
付款确认后,Paymos 向你注册的 webhook URL 发送 POST:
{
"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
}
}
验证 X-Webhook-Signature 请求头,然后履约订单。
绝不要依据浏览器事件(paymos:succeeded 或 iframe postMessage)履约订单。只有带签名的服务端 webhook 才是权威依据——浏览器信号可以被伪造。
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);
模拟测试付款
用付款模拟器把测试账单标记为已支付。不要向测试账单发送真实资产。确认注册的端点收到了带签名的事件,并且服务器在履约订单之前已用 whsec_test_… 验签。
切换到正式环境
沙盒请求和带签名的 webhook 都跑通后,在控制台切换到正式环境。没有人工审批环节。切换环境不会自动生成密钥:打开 控制台 → 开发者 → API 密钥,另行创建正式凭据(pk_live_… 和 sk_live_…)。正式 webhook 端点需要单独注册,其 whsec_live_… 密钥与测试密钥分开保存。
接收第一笔主网真实付款
用正式凭据重复一次账单请求,打开返回的 payment_url(视项目类型,它是收银台页面或 Paymos 机器人),在已开启的主网网络上发送受支持的资产。Paymos 检测到转账,并在达到所需确认数后发送带签名的正式 webhook。
从注册到第一笔主网真实付款,常规自助流程约需 10 分钟。这是上线参考值,不是 SLA:确认耗时仍取决于网络、金额和当前链上状况。