Pay-per-access
Create a payment, redirect the customer to pay, and unlock content only after the webhook confirms payment on-chain. Works for file downloads, gated articles, or API trial keys.
Flow
Customer → Clicks "Unlock" on your site
Your server → POST /payments/requests (stores payment_id -> content mapping)
Customer → Redirected to DropPay pay page
Customer → Pays with XRP wallet
DropPay → Redirects customer to your redirect_url
DropPay → Sends payment.confirmed webhook
Your server → Marks content as unlocked for this payment
Customer → Accesses content
Step 1 - Create the payment
Generate a short-lived access token and store it with the payment so you can identify what to unlock in the webhook.
unlock.py
import uuid
import requests
from flask import Flask, redirect, session
app = Flask(__name__)
BASE = "https://droppayxrp.com"
API_KEY = "dp_live_your_api_key_here"
CONTENT = {
"report_q3": {
"title": "Q3 Financial Report (PDF)",
"price_xrp": 5.0,
"file_path": "/downloads/q3-report.pdf"
}
}
@app.route("/unlock/<content_id>", methods=["POST"])
def unlock(content_id):
item = CONTENT[content_id]
access_token = str(uuid.uuid4())
response = requests.post(
f"{BASE}/payments/requests",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"amount": item["price_xrp"],
"currency": "XRP",
"description": item["title"],
"order_id": f"{content_id}:{access_token}",
"redirect_url": f"https://yoursite.com/access/{access_token}"
}
)
payment = response.json()
return redirect(BASE + payment["pay_url"])
unlock.js
import { randomUUID } from "crypto";
const BASE = "https://droppayxrp.com";
const API_KEY = "dp_live_your_api_key_here";
const CONTENT = {
report_q3: {
title: "Q3 Financial Report (PDF)",
priceXrp: 5.0,
filePath: "/downloads/q3-report.pdf"
}
};
router.post("/unlock/:contentId", async (req, res) => {
const { contentId } = req.params;
const item = CONTENT[contentId];
const accessToken = randomUUID();
const response = await fetch(`${BASE}/payments/requests`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
amount: item.priceXrp,
currency: "XRP",
description: item.title,
order_id: `${contentId}:${accessToken}`,
redirect_url: `https://yoursite.com/access/${accessToken}`
})
});
const payment = await response.json();
res.redirect(BASE + payment.pay_url);
});
Step 2 - Unlock in the webhook
Parse order_id to extract the content ID and access token. Mark the token as paid in your database before the customer returns.
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":
order_id = event["data"]["order_id"]
content_id, access_token = order_id.split(":", 1)
db.mark_access_token_paid(access_token, content_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") {
const [contentId, accessToken] = event.data.order_id.split(":", 2);
await db.markAccessTokenPaid(accessToken, contentId);
}
res.status(200).end();
}
);
Step 3 - Serve the content
The redirect_url includes the access_token in the path. Serve the file only if the token is marked as paid.
Notes
- Store access tokens in your database with a
paidboolean column and an expiry (e.g. 24 hours after payment) - Webhooks can arrive before the customer returns to your site - the access check handles both orders gracefully
- DropPay payment links expire 30 minutes after the customer opens the pay page; set
expires_in_hoursif you need a shorter window
Next steps
- Webhooks reference - full payload and security checklist
- API payments - same gating pattern for programmatic payment per API call