Skip to content

Payment links

Create a payment and share the URL directly - no redirect flow, no customer session required. Useful for invoices, digital product sales, freelance billing, or any situation where you generate the link before the customer is on your site.


Flow

Your server  →  POST /payments/requests
Your server  →  Emails pay_url to customer (or embeds it on a page)
Customer     →  Opens pay_url, pays with XRP wallet
DropPay      →  Sends payment.confirmed webhook
Your server  →  Fulfills order / marks invoice paid

No redirect URL required. The customer stays on the DropPay pay page after paying.


invoice.py
import requests

BASE = "https://droppayxrp.com"
API_KEY = "dp_live_your_api_key_here"

def create_invoice(customer_email: str, amount_usd: float, description: str, order_id: str) -> str:
    response = requests.post(
        f"{BASE}/payments/requests",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "amount": amount_usd,
            "currency": "USD",
            "description": description,
            "order_id": order_id,
            "customer_email": customer_email,  # DropPay emails a receipt on payment
            "expires_in_hours": 72
        }
    )

    payment = response.json()
    return BASE + payment["pay_url"]

# Send the link however you like
pay_url = create_invoice(
    customer_email="customer@example.com",
    amount_usd=250.00,
    description="Web design - Invoice #INV-0042",
    order_id="INV-0042"
)
send_email(to="customer@example.com", body=f"Pay here: {pay_url}")
invoice.js
const BASE = "https://droppayxrp.com";
const API_KEY = "dp_live_your_api_key_here";

async function createInvoice({ customerEmail, amountUsd, description, orderId }) {
  const response = await fetch(`${BASE}/payments/requests`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      amount: amountUsd,
      currency: "USD",
      description,
      order_id: orderId,
      customer_email: customerEmail,
      expires_in_hours: 72
    })
  });

  const payment = await response.json();
  return BASE + payment.pay_url;
}

// Send the link however you like
const payUrl = await createInvoice({
  customerEmail: "customer@example.com",
  amountUsd: 250.00,
  description: "Web design - Invoice #INV-0042",
  orderId: "INV-0042"
});
await sendEmail({ to: "customer@example.com", body: `Pay here: ${payUrl}` });

Embed on a page

Payment links work as plain <a> tags. If your page already knows the price, create the link server-side when the page loads and inject the URL.

product_page.py
@app.route("/buy/<product_id>")
def product_page(product_id):
    product = get_product(product_id)

    response = requests.post(
        f"{BASE}/payments/requests",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "amount": product["price_usd"],
            "currency": "USD",
            "description": product["name"],
            "order_id": f"{product_id}-{uuid4()}"
        }
    )

    payment = response.json()
    pay_url = BASE + payment["pay_url"]

    return render_template("product.html", product=product, pay_url=pay_url)
product.html
<a href="{{ pay_url }}" class="btn-pay">Buy with XRP - ${{ product.price_usd }}</a>
product_page.js
router.get("/buy/:productId", async (req, res) => {
  const product = await getProduct(req.params.productId);

  const response = await fetch(`${BASE}/payments/requests`, {
    method: "POST",
    headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      amount: product.priceUsd,
      currency: "USD",
      description: product.name,
      order_id: `${product.id}-${crypto.randomUUID()}`
    })
  });

  const payment = await response.json();
  res.render("product", { product, payUrl: BASE + payment.pay_url });
});
product.ejs
<a href="<%= payUrl %>" class="btn-pay">Buy with XRP - $<%= product.priceUsd %></a>

Fulfill from the webhook

No redirect means no return URL to handle - just fulfill from the webhook.

webhook.py
import hmac, hashlib, time
from flask import request, abort

WEBHOOK_SECRET = "your_webhook_secret"

@app.route("/webhook/droppay", methods=["POST"])
def droppay_webhook():
    timestamp = request.headers.get("X-DropPay-Timestamp", "")
    signature = request.headers.get("X-DropPay-Signature", "")
    body = request.get_data()

    if abs(int(time.time()) - int(timestamp)) > 300:
        abort(400)

    expected = "sha256=" + hmac.new(
        WEBHOOK_SECRET.encode(),
        timestamp.encode() + b"." + body,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        abort(400)

    event = request.json
    if event["event"] == "payment.confirmed":
        mark_invoice_paid(event["data"]["order_id"])

    return "", 200
webhook.js
import crypto from "crypto";

router.post("/webhook/droppay",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const timestamp = req.headers["x-droppay-timestamp"];
    const signature = req.headers["x-droppay-signature"];

    if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
      return res.status(400).end();
    }

    const expected = "sha256=" + crypto
      .createHmac("sha256", process.env.WEBHOOK_SECRET)
      .update(timestamp + "." + req.body)
      .digest("hex");

    if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
      return res.status(400).end();
    }

    const event = JSON.parse(req.body);
    if (event.event === "payment.confirmed") {
      await markInvoicePaid(event.data.order_id);
    }

    res.status(200).end();
  }
);

Notes

  • Pass customer_email to have DropPay email a receipt automatically on payment
  • Pass expires_in_hours to set link expiry - useful for time-sensitive invoices
  • USD amounts are converted to XRP at the live rate when the customer opens the pay page
  • The pay page shows the XRP amount and a QR code; the customer pays with any XRPL wallet

Next steps