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