1 Initiate Authentication
Partner opens NoddPay in WebView/Redirect for user authentication.
NoddPay URL:
https://noddpay.com/auth
Query String Parameters:
| Parameter |
Type |
Required |
Description |
| partnerId |
string |
✓ Sí |
Unique partner ID |
| webhookUrl |
string |
✓ Sí |
URL where NoddPay will send wallet data |
| returnUrl |
string |
✓ Sí |
Callback URL/deep link to return to app |
| email |
string |
✓ Sí |
Email para pre-llenar |
iOS Example:
let urlString = "https://noddpay.com/auth?" +
"partnerId=ABC123&" +
"webhookUrl=https://api.partner.com/noddpay-webhook&" +
"returnUrl=myapp://auth-callback&" +
"email=test@noddpay.com"
let url = URL(string: urlString)!
let webView = WKWebView()
webView.load(URLRequest(url: url))
Web Example:
const params = new URLSearchParams({
partnerId: 'ABC123',
webhookUrl: 'https://api.partner.com/noddpay-webhook',
returnUrl: 'https://partner.com/auth-callback',
email: 'test@noddpay.com'
});
window.location.href = `https://noddpay.com/auth?${params.toString()}`;
2 Immediate Callback (Frontend)
Optional but Recommended
After the user authenticates successfully, NoddPay redirects to the returnUrl.
Parameters de Callback:
| Parameter |
Description |
| status |
success, cancelled, error |
| userId |
Unique user ID in NoddPay |
| sessionId |
Authentication session ID |
| timestamp |
ISO 8601 timestamp |
Callback URL Example:
myapp://auth-callback?status=success&userId=usr_abc123&sessionId=sess_xyz789×tamp=2025-11-16T10:30:00Z
ℹNote: This callback is only to inform the frontend that the process finished.
The wallet address is NOT sent here for security. The wallet address is sent to the partner's backend via webhook.
3 Webhook with Wallet Address (Backend)
Critical
NoddPay sends a POST request to the webhookUrl with the user data and wallet address.
Webhook Payload:
{
"eventType": "user.authenticated",
"eventId": "evt_abc123xyz",
"timestamp": "2025-11-16T10:30:00Z",
"partnerId": "ABC123",
"data": {
"userId": "usr_abc123",
"email": "user@example.com",
"name": "Juan Pérez",
"walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"blockchain": "Ethereum",
"network": "Mainnet",
"currency": "USDC",
"kycStatus": "approved",
"kycLevel": "LEVEL_2",
"createdAt": "2025-11-16T10:30:00Z",
"metadata": {
"orderId": "ORDER-123",
"amount": 50
}
},
"signature": "a1b2c3d4e5f6..."
}
Webhook Headers:
POST /noddpay-webhook HTTP/1.1
Host: api.partner.com
Content-Type: application/json
X-NoddPay-Signature: sha256=a1b2c3d4e5f6...
X-NoddPay-Event-Id: evt_abc123xyz
X-NoddPay-Timestamp: 2025-11-16T10:30:00Z
User-Agent: NoddPay-Webhook/1.0
Implementación del Webhook Endpoint:
// Node.js/Express example
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const NODDPAY_WEBHOOK_SECRET = process.env.NODDPAY_WEBHOOK_SECRET;
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return `sha256=${expectedSignature}` === signature;
}
router.post('/noddpay-webhook', async (req, res) => {
const signature = req.headers['x-noddpay-signature'];
const eventId = req.headers['x-noddpay-event-id'];
// 1. Verify signature
if (!verifyWebhookSignature(req.body, signature, NODDPAY_WEBHOOK_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// 2. Verify it's not a duplicate event
const eventExists = await db.webhookEvents.findOne({ eventId });
if (eventExists) {
console.log('Duplicate event, already processed');
return res.status(200).json({ message: 'Already processed' });
}
// 3. Save event
await db.webhookEvents.create({
eventId,
payload: req.body,
processedAt: new Date()
});
// 4. Process the event
const { eventType, data } = req.body;
if (eventType === 'user.authenticated') {
const { userId, email, walletAddress, metadata } = data;
// Save user's wallet address
await db.users.update(
{ email },
{
noddpayUserId: userId,
walletAddress,
blockchain: data.blockchain,
network: data.network,
kycStatus: data.kycStatus
}
);
// If there's metadata with orderId, update the order
if (metadata && metadata.orderId) {
await db.orders.update(
{ orderId: metadata.orderId },
{
walletAddress,
status: 'wallet_connected',
readyForPayment: true
}
);
// Optional: Start pay-in process automatically
await initiatePayIn({
orderId: metadata.orderId,
toAddress: walletAddress,
amount: metadata.amount
});
}
console.log(`Wallet address received for user ${email}: ${walletAddress}`);
}
// 5. Respond 200 OK quickly
res.status(200).json({ received: true });
});
module.exports = router;
4 Procesar Pay-In
Once the partner receives the wallet address via webhook, they can process the pay-in.
async function initiatePayIn({ orderId, toAddress, amount }) {
try {
// 1. Get order
const order = await db.orders.findOne({ orderId });
// 2. Initiate USDC transfer on-chain
const transaction = await blockchainService.transferUSDC({
from: PARTNER_WALLET_ADDRESS,
to: toAddress,
amount: amount,
metadata: {
orderId,
type: 'pay-in'
}
});
// 3. Save transaction
await db.transactions.create({
orderId,
txHash: transaction.hash,
from: PARTNER_WALLET_ADDRESS,
to: toAddress,
amount,
currency: 'USDC',
blockchain: 'Ethereum',
status: 'pending',
createdAt: new Date()
});
// 4. Monitor confirmation
await monitorTransaction(transaction.hash, orderId);
console.log(`Pay-in initiated: ${transaction.hash}`);
return transaction;
} catch (error) {
console.error('Error initiating pay-in:', error);
throw error;
}
}
async function monitorTransaction(txHash, orderId) {
// Polling or websocket to monitor confirmations
const confirmations = await blockchainService.waitForConfirmations(
txHash,
3 // Wait for 3 confirmations
);
if (confirmations >= 3) {
// Update order as paid
await db.orders.update(
{ orderId },
{
status: 'paid',
paidAt: new Date(),
txHash
}
);
await db.transactions.update(
{ txHash },
{ status: 'confirmed' }
);
console.log(`Transaction confirmed: ${txHash}`);
}
}