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.
Create a payment link
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_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 });
});
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_emailto have DropPay email a receipt automatically on payment - Pass
expires_in_hoursto 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
- Webhooks reference - full payload, signature verification, security checklist
- E-commerce checkout - redirect the customer back to your site after payment
- Pay-per-access - gate downloads or content behind a one-time payment