Everything you need to integrate Prilana Pay into your site. Register at /sites to get your API key.
Go to /sitesand fill out the registration form. You'll receive a merchantId and apiKeyimmediately. Save your API key — it's shown once.
<script src="https://prilana.com/widget.js"></script>
<prilana-pay
merchant-id="YOUR_MERCHANT_ID"
api-key="YOUR_API_KEY"
amount="9.99"
label="Premium Access"
></prilana-pay>document.querySelector('prilana-pay')
.addEventListener('prilana:success', (e) => {
const { txSignature, amount, orderId } = e.detail;
// Verify server-side, then unlock content
fetch('/your-api/verify', {
method: 'POST',
body: JSON.stringify({ txSignature })
});
});curl -X POST https://prilana.com/api/merchant/verify-payment \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"txSignature":"5UfDuX...","merchantId":"YOUR_MERCHANT_ID"}'Set your webhook URL in the dashboard settings. We'll POST payment confirmations to your endpoint in real time.
All authenticated endpoints accept your API key via the Authorization header or merchant-specific headers.
Authorization: Bearer YOUR_API_KEYX-API-Key: YOUR_API_KEY
X-Merchant-Id: YOUR_MERCHANT_IDYour API key is shown once at registration. If you lose it, contact support@prilana.comto rotate it. Never expose your API key in client-side code (the widget's api-keyattribute is safe — it only enables pre-flight verification checks, not payment operations).
The widget is a Web Component that handles wallet connection, age verification pre-check, payment UI, and transaction confirmation. Works on any HTML page.
| Attribute | Required | Description |
|---|---|---|
| merchant-id | Yes | Your Prilana merchant ID (prln_m_...) |
| amount | Yes | Payment amount in token units (e.g. "9.99") |
| api-key | Recommended | Your API key. Enables pre-flight verification check before payment. |
| label | No | Description shown to the buyer in the payment modal |
| order-id | No | Your order reference. Included in webhooks and events. |
| theme | No | "dark" (default) or "light" |
| Event | Detail |
|---|---|
| prilana:success | { txSignature, amount, orderId, merchantAmount, fee } |
| prilana:error | { error } |
| prilana:cancel | Buyer closed the payment modal |
<script src="https://prilana.com/widget.js"></script>
<prilana-pay
merchant-id="prln_m_abc123"
api-key="pk_live_..."
amount="19.99"
label="Premium Membership"
order-id="order_456"
></prilana-pay>
<script>
const widget = document.querySelector('prilana-pay');
widget.addEventListener('prilana:success', (e) => {
console.log('TX:', e.detail.txSignature);
console.log('Net:', e.detail.merchantAmount);
// Unlock content or redirect
});
widget.addEventListener('prilana:error', (e) => {
console.error('Payment failed:', e.detail.error);
});
widget.addEventListener('prilana:cancel', () => {
console.log('Buyer cancelled');
});
</script>For React and Next.js apps, use the @prilana/react package instead of the raw widget. It handles script loading, TypeScript types, and wires events to React callbacks.
npm install @prilana/reactimport { PrilanaPay } from '@prilana/react';
export default function BuyButton() {
return (
<PrilanaPay
merchantId="prln_m_abc123"
apiKey="pk_live_..."
amount="9.99"
label="Premium Access"
orderId="order_456"
onSuccess={(e) => {
console.log('Paid!', e.txSignature);
fetch('/api/unlock', {
method: 'POST',
body: JSON.stringify({ tx: e.txSignature }),
});
}}
onError={(e) => console.error(e.error)}
onCancel={() => console.log('Cancelled')}
/>
);
}Always verify payments on your backend before unlocking content. Never trust the client event alone.
import { verifyPayment } from '@prilana/react';
export async function POST(req: Request) {
const { tx } = await req.json();
const result = await verifyPayment({
txSignature: tx,
merchantId: 'prln_m_abc123',
apiKey: process.env.PRILANA_API_KEY!,
});
if (!result.valid) {
return Response.json({ error: 'Invalid payment' }, { status: 400 });
}
// Unlock content for this user
return Response.json({ unlocked: true });
}import { verifyWebhookSignature } from '@prilana/react';
export async function POST(req: Request) {
const body = await req.text();
const sig = req.headers.get('x-prilana-signature')!;
const ts = req.headers.get('x-prilana-timestamp')!;
if (!verifyWebhookSignature(body, sig, ts, process.env.WEBHOOK_SECRET!)) {
return Response.json({ error: 'Bad signature' }, { status: 401 });
}
const event = JSON.parse(body);
// event.txSignature, event.amount, event.buyerWallet...
return Response.json({ ok: true });
}/api/pay/create— Build an unsigned payment transactionCalled by the widget automatically. Returns a serialized Solana transaction for the buyer to sign.
{
"merchantId": "prln_m_abc123",
"amount": "9.99",
"buyerWallet": "7nYBm...",
"orderId": "order_456" // optional
}{
"transaction": "base64-encoded-transaction...",
"blockhash": "...",
"lastValidBlockHeight": 123456,
"orderIdHex": "abc123...",
"details": {
"amount": "9990000",
"fee": "299700",
"merchantAmount": "9690300",
"feeBps": 300,
"tokenMint": "CSi5eSLSkkbLHqABicuejQ393KAvPKiWjEEX4o8LzGvc",
"decimals": 6
}
}{
"error": "Age verification required",
"verifyUrl": "https://prilana.com/verify"
}/api/pay/confirm— Confirm a signed payment on-chainCalled by the widget after the buyer signs. Parses the on-chain transaction, stores the payment record, and triggers your webhook.
{
"txSignature": "5UfDuX...",
"merchantId": "prln_m_abc123"
}{
"success": true,
"payment": {
"txSignature": "5UfDuX...",
"amount": 9990000,
"fee": 299700,
"merchantAmount": 9690300,
"buyerWallet": "7nYBm..."
}
}/api/merchant/verify-paymentAPI Key— Server-side payment verificationUse this to verify a payment on your backend before unlocking content. Authenticate with your API key.
curl -X POST https://prilana.com/api/merchant/verify-payment \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"txSignature": "5UfDuX...",
"merchantId": "prln_m_abc123",
"expectedAmount": 9.99
}'{
"valid": true,
"payment": {
"txSignature": "5UfDuX...",
"amount": 9990000,
"fee": 299700,
"merchantAmount": 9690300,
"buyerWallet": "7nYBm...",
"createdAt": "2026-03-19T12:00:00Z"
}
}/api/verify/check?wallet=...API Key— Check a wallet's age verification statusQuery whether a buyer is age-verified before allowing a purchase. Requires your merchant API key. The widget calls this automatically if you provide the api-key attribute.
curl https://prilana.com/api/verify/check?wallet=7nYBm... \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Merchant-Id: prln_m_abc123"{
"verified": true,
"level": 1,
"method": "rekognition_selfie",
"verifiedAt": "2026-03-19T10:30:00Z",
"expiresAt": "2027-03-19T10:30:00Z"
}{
"verified": false,
"level": 0
}Use case: Gate content or purchases behind age verification. If verified is false, redirect the user to https://prilana.com/verify to complete verification. Verification is a one-time process per wallet.
Configure your webhook URL in the dashboard settings. Prilana sends a POST request to your endpoint for every confirmed payment.
{
"event": "payment.confirmed",
"data": {
"txSignature": "5UfDuX...",
"merchantId": "prln_m_abc123",
"amount": 9990000,
"fee": 299700,
"merchantAmount": 9690300,
"buyerWallet": "7nYBm...",
"orderId": "order_456",
"timestamp": "2026-03-19T12:00:00Z"
}
}| Header | Description |
|---|---|
| X-Prilana-Signature | HMAC-SHA256 hex digest of `${timestamp}.${body}` |
| X-Prilana-Timestamp | Unix timestamp (ms) when the webhook was sent. Reject if older than 5 minutes. |
The signature covers `${timestamp}.${body}`to prevent replay attacks. Always verify the timestamp is recent (< 5 minutes).
import crypto from 'crypto';
function verifyWebhook(body, signature, timestamp, webhookSecret) {
// Reject old webhooks (replay protection)
if (Math.abs(Date.now() - parseInt(timestamp)) > 300000) return false;
// Signature covers timestamp.body
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(timestamp + '.' + body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
);
}
// Express handler:
app.post('/webhooks/prilana', (req, res) => {
const sig = req.headers['x-prilana-signature'];
const ts = req.headers['x-prilana-timestamp'];
if (!verifyWebhook(JSON.stringify(req.body), sig, ts, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const { txSignature, amount, buyerWallet } = req.body;
// Process the payment...
res.status(200).send('OK');
});We retry aggressively to ensure delivery:
Immediate retries: 4 attempts (0s, 1s, 5s, 30s)
Persistent queue: If all immediate retries fail, the webhook is queued in our database with exponential backoff: 1min, 5min, 30min, 2hr, 4hr, 8hr, 12hr
Max attempts: 10 total over ~40 hours
After exhaustion: Marked as failed. You can always verify payments manually via the API.
| Code | Meaning | Action |
|---|---|---|
| 400 | Bad request — missing or invalid parameters | Check the request body matches the API spec |
| 401 | Unauthorized — invalid or missing API key | Verify your API key and merchant ID |
| 403 | Buyer not age-verified | Redirect buyer to https://prilana.com/verify |
| 404 | Merchant or payment not found | Verify the merchant ID or transaction signature |
| 409 | Wallet already verified | User has already completed verification |
| 429 | Rate limit exceeded | Wait and retry. Limits: 60 req/min for verify/check, 30 req/min for verify-payment |
| 500 | Server error | Retry after a moment. Contact support if persistent. |
Email support@prilana.com or check your merchant dashboard for integration status.