NoddPay API Keys: PK vs SK
Guía actualizada para WebView Integration
Public Key (pk) vs Secret Key (sk)
PUBLIC KEY (pk)
ndpy_live_pk_a1b2c3d4e5f6...
Uso:
- ✓ Puede ir en URLs
- ✓ Segura en código frontend
- ✓ Visible en HTML/JavaScript
- ✓ Para iniciar flujos de auth
Permisos:
- Solo lectura
- Iniciar autenticación
- Sin acceso a datos sensibles
SECRET KEY (sk)
ndpy_live_sk_xyz789abcdef...
Uso:
- ✗ NUNCA en URLs
- ✗ NUNCA en frontend
- ✓ Solo en backend/servidor
- ✓ Variables de entorno
Permisos:
- Acceso completo a la API
- Operaciones sensibles
- Gestión de cuentas
Estructura de Keys para NoddPay
Cada Partner recibe 3 credenciales:
| Credencial |
Formato |
Uso |
Ubicación |
| Partner ID |
ndpy_live_ptr_... |
Identificador único del partner |
Frontend (URL) |
| Public Key (pk) |
ndpy_live_pk_... |
Autenticar requests del frontend |
Frontend (URL) |
| Webhook Secret |
whsec_... |
Verificar firma de webhooks |
Backend (privado) |
1. Public Key (pk) - Para WebView URL
// Formato
ndpy_{environment}_pk_{64_random_hex_chars}
// Ejemplos
ndpy_live_pk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
ndpy_test_pk_9z8y7x6w5v4u3t2s1r0q9p8o7n6m5l4k3j2i1h0g9f8e7d6c5b4
2. Webhook Secret - Para verificar webhooks
// Formato
whsec_{64_random_hex_chars}
// Ejemplo
whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
3. Partner ID - Identificador simple
// Formato
ndpy_{environment}_ptr_{12_random_alphanumeric}
// Ejemplo
ndpy_live_ptr_abc123xyz789
Flujo Completo con PK
Frontend del Partner (Mobile/Web):
// React Native / JavaScript
const NODDPAY_PARTNER_ID = 'ptr_abc123xyz';
const NODDPAY_PUBLIC_KEY = 'ndpy_live_pk_a1b2c3d4e5f6g7h8...';
function openNoddPayAuth() {
const params = new URLSearchParams({
partnerId: NODDPAY_PARTNER_ID,
pk: NODDPAY_PUBLIC_KEY, // ← Public Key (segura)
returnUrl: 'myapp://auth-callback'
});
const url = `https://noddpay.com/auth?${params.toString()}`;
// Mobile: Abrir en WebView
WebView.navigate(url);
// Web: Redirect
window.location.href = url;
}
// Este código puede estar en el frontend sin problemas ✅
Backend del Partner (Node.js):
// .env file (PRIVADO)
NODDPAY_WEBHOOK_SECRET=whsec_a1b2c3d4e5f6g7h8i9j0k1l2...
// webhook-handler.js
const crypto = require('crypto');
router.post('/noddpay-webhook', (req, res) => {
const signature = req.headers['x-noddpay-signature'];
const rawBody = JSON.stringify(req.body);
// Verificar firma usando WEBHOOK SECRET (privada) ✅
const expectedSignature = crypto
.createHmac('sha256', process.env.NODDPAY_WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
if (`sha256=${expectedSignature}` !== signature) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Procesar webhook...
const { userId, email, walletAddress } = req.body.data;
res.status(200).json({ received: true });
});
Validación en NoddPay Backend
1. Validar Public Key en la URL:
// Lambda que recibe request del WebView
export async function handler(event) {
const { pk, partnerId, returnUrl } = event.queryStringParameters;
// 1. Validar formato de PK
const pkPattern = /^ndpy_(live|test)_pk_[a-f0-9]{64}$/;
if (!pkPattern.test(pk)) {
return {
statusCode: 400,
body: 'Invalid public key format'
};
}
// 2. Hashear y buscar en DynamoDB
const pkHash = createHash('sha256').update(pk).digest('hex');
const result = await dynamoDB.query({
TableName: 'PartnerKeys',
IndexName: 'PublicKeyHashIndex',
KeyConditionExpression: 'pkHash = :hash',
ExpressionAttributeValues: {
':hash': pkHash
}
}).promise();
if (!result.Items || result.Items.length === 0) {
return {
statusCode: 401,
body: 'Invalid public key'
};
}
const partner = result.Items[0];
// 3. Validar que el partnerId coincide
if (partner.partnerId !== partnerId) {
return {
statusCode: 401,
body: 'Partner ID mismatch'
};
}
// 4. Validar returnUrl está en whitelist
const allowedUrls = partner.allowedReturnUrls || [];
if (!allowedUrls.some(url => returnUrl.startsWith(url))) {
return {
statusCode: 400,
body: 'Return URL not whitelisted'
};
}
// 5. Todo validado, continuar con auth
return {
statusCode: 200,
body: JSON.stringify({
partnerId: partner.partnerId,
partnerName: partner.name,
webhookUrl: partner.webhookUrl,
webhookSecret: partner.webhookSecret
})
};
}
2. Enviar Webhook con Webhook Secret:
// Lambda que envía webhook después de auth exitosa
export async function sendWebhook(userId, partnerId) {
// 1. Obtener datos del partner
const partner = await getPartner(partnerId);
// 2. Construir payload
const payload = {
eventType: 'user.authenticated',
eventId: `evt_${randomBytes(16).toString('hex')}`,
timestamp: new Date().toISOString(),
data: {
userId,
email: user.email,
walletAddress: user.walletAddress,
blockchain: 'Ethereum',
network: 'Mainnet'
}
};
// 3. Firmar con WEBHOOK SECRET del partner
const signature = crypto
.createHmac('sha256', partner.webhookSecret)
.update(JSON.stringify(payload))
.digest('hex');
// 4. Enviar POST al partner
const response = await axios.post(
partner.webhookUrl,
payload,
{
headers: {
'Content-Type': 'application/json',
'X-NoddPay-Signature': `sha256=${signature}`,
'X-NoddPay-Event-Id': payload.eventId
},
timeout: 10000
}
);
return response;
}
Estructura en DynamoDB
// Tabla: PartnerKeys
{
"partnerId": "ptr_abc123xyz", // Partition Key
"keyId": "key_001", // Sort Key
// Public Key
"publicKey": "ndpy_live_pk_...", // Para mostrar en dashboard
"publicKeyHash": "sha256_hash_of_pk", // Para validación
// Webhook Secret
"webhookSecret": "whsec_...", // Para firmar webhooks
"webhookSecretHash": "sha256_hash_whsec", // Para rotación
// Configuración
"environment": "live", // live | test
"status": "active", // active | revoked
"webhookUrl": "https://partner.com/webhook",
"allowedReturnUrls": [
"myapp://",
"https://partner.com/callback"
],
// Metadata
"partnerName": "Acme Corp",
"contactEmail": "dev@acme.com",
"createdAt": "2024-01-15T10:30:00Z",
"lastUsedAt": "2024-01-20T14:22:00Z",
// Rate Limiting
"rateLimitPerHour": 1000,
"requestCount": 45,
"resetAt": "2024-01-20T15:00:00Z"
}
Generación de Keys al Registrar Partner
// Lambda: Create Partner Keys
import { randomBytes, createHash } from 'crypto';
export async function createPartnerKeys(partnerId: string, environment: 'live' | 'test') {
// 1. Generar Public Key
const pkToken = randomBytes(32).toString('hex');
const publicKey = `ndpy_${environment}_pk_${pkToken}`;
const publicKeyHash = createHash('sha256').update(publicKey).digest('hex');
// 2. Generar Webhook Secret
const whToken = randomBytes(32).toString('hex');
const webhookSecret = `whsec_${whToken}`;
const webhookSecretHash = createHash('sha256').update(webhookSecret).digest('hex');
// 3. Guardar en DynamoDB
await dynamoDB.put({
TableName: 'PartnerKeys',
Item: {
partnerId,
keyId: `key_${Date.now()}`,
publicKey: publicKey.substring(0, 25) + '...', // Solo prefijo
publicKeyHash,
webhookSecret: webhookSecret.substring(0, 15) + '...', // Solo prefijo
webhookSecretHash,
environment,
status: 'active',
createdAt: new Date().toISOString()
}
}).promise();
// 4. Retornar keys completas (SOLO UNA VEZ)
return {
partnerId,
publicKey, // Mostrar completa
webhookSecret, // Mostrar completa
message: 'Guarda estas credenciales de forma segura. No las volveremos a mostrar.'
};
}
// Uso
const keys = await createPartnerKeys('ptr_abc123', 'live');
console.log(keys);
/*
{
partnerId: 'ptr_abc123',
publicKey: 'ndpy_live_pk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6...',
webhookSecret: 'whsec_9z8y7x6w5v4u3t2s1r0q9p8o7n6m5l4k...',
message: 'Guarda estas credenciales...'
}
*/
Resumen Ejecutivo
Para NoddPay WebView Integration:
| Credencial |
Formato |
Dónde se usa |
Exponer? |
| Partner ID |
ptr_abc123xyz |
URL del WebView |
✓ Sí |
| Public Key |
ndpy_live_pk_... |
URL del WebView |
✓ Sí |
| Webhook Secret |
whsec_... |
Backend (verificar webhook) |
✗ No |
URL de Ejemplo:
https://noddpay.com/auth?partnerId=ptr_abc123&pk=ndpy_live_pk_a1b2c3...&returnUrl=myapp://callback
Seguridad:
- ✓ Public Key puede ir en la URL sin problema
- ✓ Tiene permisos limitados (solo iniciar auth)
- ✓ NoddPay valida que el PK pertenece al partnerId
- ✓ Webhook Secret NUNCA se expone, solo en backend
- ✓ Todo está hasheado en la base de datos