PlirinRequest early access
← All articles

Integrating Stablecoin Payments with the Plirin API: A Developer's Guide

APIdevelopersintegration

If you’re building a business that wants to accept crypto payments, you probably don’t want to deal with a black-box payment processor that treats stablecoins like an afterthought. You need an API you can actually trust, one built for the reality of how businesses move money today. That’s where Plirin’s REST API, webhooks, and SDKs come in — and this guide walks you through everything, from your first sandbox test to collecting real payments in production.

Let’s get practical. You’ll learn how to build a payment flow, handle webhooks that trigger when customers pay, and deploy with confidence.

First, a quick reality check. Plirin’s payment links are dead simple — create a link, share it, get paid. No code required. But if you’re running a SaaS, e-commerce platform, or any app where payment is core to your product, you need deeper control. You need to:

  • Create charges programmatically when a customer checks out
  • Store payment states in your own database
  • Trigger custom workflows when payments land (send an order confirmation, provision an account, run a background job)
  • Build a branded checkout experience that feels like part of your app

That’s what the API gives you. It’s the difference between slapping a link on your site and building real crypto commerce infrastructure.

Understanding the Plirin Payment Flow

Before you write a line of code, understand how money moves:

  1. You create a charge via the API with an amount, description, and optional customer metadata
  2. We generate a payment address on the blockchain you specify (Solana or EVM chains)
  3. The customer sends USDC or USDT to that address
  4. We detect the payment and fire a webhook to your server
  5. Your app reacts — mark the order as paid, unlock the feature, send a receipt

The beauty here is that funds flow directly to your wallet. No middleman holding your money. No settlement delays. Just transparent, instant settlement on-chain.

Getting Started: Authentication and Credentials

Head to your Plirin dashboard and navigate to Settings > API Keys. You’ll see two things:

  • Public Key — safe to embed in frontend code, used to initialize payment flows
  • Secret Key — never share this. Use it server-side to create charges and verify webhooks

Keep your secret key in an environment variable. Never commit it to git. If you accidentally expose it, rotate it immediately from the dashboard.


PLIRIN_SECRET_KEY=sk_live_your_actual_key_here
PLIRIN_PUBLIC_KEY=pk_live_your_public_key_here

Using the REST API: Creating Your First Charge

The Plirin API follows REST conventions. All requests go to https://api.plirin.com/v1/ and use standard HTTP methods. Let’s create a charge.

Basic Request

curl -X POST https://api.plirin.com/v1/charges \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 2500,
    "currency": "usd",
    "description": "Premium plan upgrade",
    "blockchain": "solana",
    "customer_id": "cust_user123"
  }'

The response includes:

{
  "id": "ch_abc123xyz",
  "amount": 2500,
  "currency": "usd",
  "status": "pending",
  "payment_address": "EPjFWdd5Au17h7xNceVBj4AxC23yUvoMRRy8uK2EyUU",
  "blockchain": "solana",
  "expires_at": "2025-01-15T14:32:00Z",
  "created_at": "2025-01-15T14:22:00Z"
}

That payment_address is where your customer sends money. You can display it as a QR code, a text field, or pass it to a wallet connector like Magic or Phantom.

SDK Support: TypeScript and Python

Writing raw curl requests gets old. Plirin provides official SDKs that handle auth, parsing, and error handling for you.

TypeScript/JavaScript

import { Plirin } from "@plirin/sdk";

const client = new Plirin({
  secretKey: process.env.PLIRIN_SECRET_KEY,
});

async function createCharge(userId: string, amountUsd: number) {
  const charge = await client.charges.create({
    amount: Math.round(amountUsd * 100), // Convert to cents
    currency: "usd",
    description: `Payment for user ${userId}`,
    blockchain: "solana",
    customer_id: userId,
  });

  return charge;
}

// Usage
const charge = await createCharge("user_456", 25.00);
console.log(`Pay to: ${charge.payment_address}`);

Python

from plirin import Plirin

client = Plirin(secret_key=os.getenv("PLIRIN_SECRET_KEY"))

def create_charge(user_id: str, amount_usd: float):
    charge = client.charges.create(
        amount=int(amount_usd * 100),  # Convert to cents
        currency="usd",
        description=f"Payment for user {user_id}",
        blockchain="solana",
        customer_id=user_id
    )
    return charge

charge = create_charge("user_456", 25.00)
print(f"Pay to: {charge.payment_address}")

Both SDKs handle request signing, retries on transient failures, and give you type hints. If you’re using FastAPI, Django, Express, or Next.js, the SDK integrates cleanly.

Webhooks: Reacting When Customers Pay

This is where your app comes alive. Instead of polling Plirin asking “Is it paid yet?”, we tell you when a payment lands. Webhooks are HTTP POST requests from our servers to an endpoint you control.

Setting Up Your Webhook Endpoint

Create an endpoint that accepts JSON and verifies the signature:

import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.PLIRIN_WEBHOOK_SECRET;

app.post("/webhooks/plirin", (req, res) => {
  // Verify the signature
  const signature = req.headers["x-plirin-signature"] as string;
  const body = JSON.stringify(req.body);
  const hash = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(body)
    .digest("hex");

  if (hash !== signature) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Process the event
  const event = req.body;

  if (event.type === "charge.paid") {
    const charge = event.data;
    console.log(`Charge ${charge.id} paid for customer ${charge.customer_id}`);

    // Update your database
    db.payments.update(
      { charge_id: charge.id },
      { status: "paid", paid_at: new Date() }
    );

    // Trigger your workflow
    sendOrderConfirmation(charge.customer_id);
    provisioning(charge.customer_id);
  }

  res.json({ success: true });
});

Important: Always verify the signature. It proves the webhook came from Plirin, not some attacker spoofing a request.

Registering the Webhook

Go to Settings > Webhooks in your dashboard and add your endpoint URL. You can test it directly from the dashboard — Plirin will send a sample event and show you the response.

Sandbox vs. Mainnet: Testing Before You Go Live

Never test real transactions on mainnet. Use our sandbox environment first.

Sandbox Setup

  1. Create a separate Plirin account (or use a sandbox project)
  2. Use sandbox API keys from Settings > API Keys > Sandbox
  3. Requests go to https://sandbox-api.plirin.com/v1/
  4. Fund a test wallet with testnet tokens (Solana devnet or Ethereum sepolia, depending on your chain)

In sandbox, charges never expire, and you can manually mark them as paid from the dashboard to test your webhook handling.

Moving to Mainnet

Once you’ve tested:

  1. Switch to mainnet API keys
  2. Update your endpoint from sandbox-api.plirin.com to api.plirin.com
  3. Make sure your webhook secret is set
  4. Run a small test transaction with real funds (or ask Plirin for a test credit)
  5. Monitor your logs for the webhook

Error Handling and Retries

Stuff breaks. Networks hiccup. Your server goes down for 30 seconds. Build for it.

API Errors

try {
  const charge = await client.charges.create({
    amount: 2500,
    currency: "usd",
    blockchain: "solana",
  });
} catch (error) {
  if (error.code === "network_error") {
    console.error("Network failed, retry in 5 seconds");
    // Implement exponential backoff
  } else if (error.code === "rate_limit") {
    console.error("Hit rate limit, back off");
  } else {
    console.error("API error:", error.message);
  }
}

Webhook Retries

Plirin retries failed webhooks (you return a non-2xx status) up to 5 times over 24 hours. Always be idempotent — if you receive the same webhook twice, processing it twice shouldn’t break anything.

// Good: Idempotent
db.payments.update(
  { charge_id: charge.id },
  { status: "paid", paid_at: new Date() },
  { upsert: true }
);

// Bad: Not idempotent
db.payments.create({ charge_id: charge.id, status: "paid" });
// Second webhook triggers duplicate!

Choosing Your Blockchain: Solana or EVM?

When you create a charge, specify blockchain: "solana" or blockchain: "ethereum" (or Base, Polygon, etc. for EVM chains). This determines which network the payment address lives on.

Solana — faster confirmations, lower fees, simpler integration. If your customers have Phantom or Marinade wallets, they’re ready to go.

Ethereum/Base/Polygon — more wallet options (MetaMask, Coinbase Wallet, etc.), wider adoption. Slightly higher fees and confirmation times.

For detailed chain comparisons, check out our guide on choosing the right blockchain for your payments.

Building a Complete Checkout Flow

Let’s tie it together. Here’s a real example: a SaaS charging for plan upgrades.

Frontend:

async function handleCheckout(planId: string, amount: number) {
  const response = await fetch("/api/checkout", {
    method: "POST",
    body: JSON.stringify({ plan_id: planId, amount }),
  });

  const { payment_address, charge_id } = await response.json();

  // Show a modal with QR code + address
  showPaymentModal({
    address: payment_address,
    amount,
    onConfirm: async () => {
      // Poll or wait for webhook
      const result = await waitForPayment(charge_id);
      if (result.status === "paid") {
        // Redirect to dashboard
        window.location.href = "/dashboard";
      }
    },
  });
}

Backend (Node.js):

app.post("/api/checkout", async (req, res) => {
  const { plan_id, amount } = req.body;
  const userId = req.user.id;

  // Create a charge
  const charge = await client.charges.create({
    amount: Math.round(amount * 100),
    currency: "usd",
    description: `Plan upgrade: ${plan_id}`,
    blockchain: "solana",
    customer_id: userId,
  });

  // Store it in your DB
  await db.orders.insert({
    user_id: userId,
    charge_id: charge.id,
    plan_id,
    status: "pending",
    created_at: new Date(),
  });

  res.json({
    payment_address: charge.payment_address,
    charge_id: charge.id,
  });
});

// Webhook handler (from earlier)
app.post("/webhooks/plirin", async (req, res) => {
  const event = req.body;

  if (event.type === "charge.paid") {
    const order = await db.orders.findOne({
      charge_id: event.data.id,
    });

    if (order) {
      await db.orders.update(
        { charge_id: event.data.id },
        { status: "paid", paid_at: new Date() }
      );

      // Upgrade the user's plan
      await db.users.update(
        { id: order.user_id },
        { plan: order.plan_id, upgrade_date: new Date() }
      );
    }
  }

  res.json({ success: true });
});

This is a real, working pattern. Customer clicks checkout → you create a charge → they pay → webhook tells you → you unlock the feature.

Advanced: Handling Refunds and Disputes

Sometimes you need to refund a customer. Plirin doesn’t automatically reverse on-chain transactions (that’s not how blockchain works), but you can handle refunds in your app:

  1. Check if the customer’s charge has a refund_address set
  2. Send USDC or USDT directly to that address from your wallet
  3. Mark the order as refunded in your database

For a deeper dive, read our guide on stablecoin refund management.

Monitoring, Logging, and Debugging

In production, you need visibility:

  • Log all API calls: timestamps, amounts, customer IDs, response codes
  • Monitor webhook delivery: track which webhooks succeed vs. fail
  • Set alerts: if webhooks fail for more than 5 minutes, page on-call
  • Track charge creation to payment: measure how long customers take to pay

Use Plirin’s dashboard to view all charges and events. Export transaction data for accounting via the API or CSV export.

Security Checklist Before Launch

  • Store API keys in environment variables, never in code
  • Verify webhook signatures on every request
  • Use HTTPS for your webhook endpoint
  • Implement rate limiting on your API routes
  • Test error handling (network failures, timeouts)
  • Audit database queries for SQL injection
  • Use a dedicated business wallet, never a personal one
  • Rotate API keys regularly
  • Review Plirin’s crypto payment compliance guide for KYB/AML requirements

Next Steps: From Integration to Scale

Once you’ve integrated the API and taken your first live payment, you’re ready to optimize. Consider:

  • Recurring billing: for subscriptions, use recurring billing with stablecoins
  • Multi-chain strategy: accept payments on both Solana and Ethereum to reach more customers
  • Webhook automation: trigger internal systems (inventory, fulfillment, analytics)
  • Analytics: track which chains, amounts, and customer types drive revenue

The API gives you the foundation. Your product, and how you use it, is what matters.


Ready to accept stablecoin payments via API? Create a free account and start building in sandbox today. Check out Plirin’s pricing to see which tier fits your business, and when you’re ready, head to the waitlist to unlock mainnet access.