7.7 KiB
NOWPayments IPN Listener
A TypeScript Express application for receiving and processing Instant Payment Notifications (IPN) from NOWPayments.
Features
- ✅ Secure IPN signature validation using HMAC SHA512
- ✅ TypeScript for type safety
- ✅ Handles all NOWPayments payment statuses
- ✅ MySQL/MariaDB database integration
- ✅ Automatic payment processing: moves finished/confirmed payments from
pending_orderstosales - ✅ 10-minute reservation mechanism to prevent overselling
- ✅ Expiration checking and automatic cleanup of expired orders
- ✅ Final inventory validation before creating sales
- ✅ Transaction-safe database operations
- ✅ Idempotent IPN processing (handles duplicate callbacks)
- ✅ Error handling and logging
Setup
1. Install Dependencies
npm install
2. Configure Environment Variables
Copy .env.example to .env and fill in your NOWPayments IPN Secret Key:
cp .env.example .env
Edit .env and configure:
NOWPayments IPN Secret Key:
- Go to NOWPayments Dashboard → Store Settings → IPN Secret Key
- Generate a new key if you don't have one
- Add it to the
.envfile
Database Configuration:
- Set your MySQL/MariaDB connection details:
DB_HOST- Database host (default: localhost)DB_PORT- Database port (default: 3306)DB_USER- Database usernameDB_PASSWORD- Database passwordDB_NAME- Database name (default: cbd420)
3. Database Migration
The application requires the expires_at column in the pending_orders table for the 10-minute reservation mechanism. Run the migration:
mysql -u your_user -p your_database < migrations/add_expires_at_to_pending_orders.sql
Or manually add the column:
ALTER TABLE `pending_orders`
ADD COLUMN `expires_at` datetime NOT NULL DEFAULT (DATE_ADD(NOW(), INTERVAL 10 MINUTE))
AFTER `created_at`;
ALTER TABLE `pending_orders`
ADD INDEX `idx_expires_at` (`expires_at`);
4. Build the Project
npm run build
5. Run the Server
Development mode (with auto-reload):
npm run dev
Production mode:
npm start
The server will start on port 3000 (or the port specified in your .env file).
Endpoints
POST /ipn
Receives IPN notifications from NOWPayments. This endpoint:
- Validates the request signature
- Processes payment status updates
- Returns 200 OK to acknowledge receipt
GET /health
Health check endpoint to verify the server is running.
IPN Callback URL Setup
When creating a payment via the NOWPayments API, include the ipn_callback_url parameter:
{
"price_amount": 100,
"price_currency": "usd",
"pay_currency": "btc",
"ipn_callback_url": "https://yourdomain.com/ipn",
// ... other parameters
}
Payment Statuses
The listener handles the following payment statuses:
waiting- Payment is waitingconfirming- Payment is being confirmed on blockchainconfirmed- Payment confirmed on blockchainsending- Payment is being sentpartially_paid- Payment partially receivedfinished- Payment completed successfullyfailed- Payment failedrefunded- Payment refundedexpired- Payment expired
Database Integration
The application implements a 10-minute reservation mechanism to prevent race conditions when multiple buyers attempt to purchase the last available units simultaneously.
Payment Processing Flow
When a payment status is finished or confirmed, the system:
- Find Pending Order - Looks up the pending order by
payment_idorinvoice_id - Check Expiration - Verifies the pending order hasn't expired (10-minute window)
- Validate Payment Status - Processes based on status:
finishedorconfirmed→ Proceed to create salefailedorexpired→ Delete pending orderwaiting,confirming→ Acknowledge and wait
- Final Inventory Check - Validates inventory is still available before creating sale
- Create Sale Record - Inserts into
salestable and deletes frompending_orders
All operations are performed within a database transaction to ensure data consistency and prevent race conditions.
Database Schema
The application expects the following tables (as defined in cbd420(1).sql + migration):
-
pending_orders- Stores pending payment orders with 10-minute reservationspayment_id(unique) - NOWPayments payment/invoice IDorder_id(unique) - Internal order ID (format: SALE-{timestamp}-{drop_id}-{buyer_id})drop_id- Reference to drops tablebuyer_id- Reference to buyers tablesize- Order size (in grams)price_amount- Payment amountprice_currency- Payment currency (default: 'chf')created_at- Order creation timestampexpires_at- Expiration timestamp (10 minutes from creation) - REQUIRED
-
sales- Stores completed salesdrop_id- Reference to drops tablebuyer_id- Reference to buyers tablesize- Sale size (in grams)payment_id- NOWPayments payment ID (matches pending_orders.payment_id)created_at- Sale creation timestamp
-
drops- Product drop informationid- Drop IDsize- Available sizeunit- Unit of measurement ('g' or 'kg')
Inventory Calculation
Available inventory is calculated as:
Available = drop.size - (SUM(sales.size) + SUM(pending_orders.size WHERE expires_at > NOW()))
The system automatically handles unit conversion (kg to grams) when calculating inventory.
Key Features
- Expiration Handling: Pending orders expire after 10 minutes and are automatically excluded from inventory calculations
- Inventory Validation: Final inventory check ensures no overselling occurs between payment initiation and completion
- Idempotency: IPN callbacks can be processed multiple times safely (checks for existing sales)
- Transaction Safety: All database operations use transactions to ensure atomicity
IPN Callback Processing
The IPN handler follows this flow for each callback:
- Find Pending Order - Searches by
payment_idorinvoice_id(tries both) - Check Expiration - Verifies
expires_at > NOW()- expired orders are rejected - Payment Status Processing:
finishedorconfirmed→ Creates sale (after inventory check)failedorexpired→ Deletes pending order- Other statuses → Acknowledged, waiting for final status
- Final Inventory Check - Re-validates inventory before creating sale (within transaction)
- Create Sale - Atomically creates sale and deletes pending order
Important Notes
- IPN callbacks may be sent multiple times - the system handles this with idempotency checks
- Expired pending orders are automatically excluded from inventory calculations
- Always returns HTTP 200 to NOWPayments (even on errors) to prevent retries
- Inventory is checked within a database transaction to prevent race conditions
Security
- All IPN requests are validated using HMAC SHA512 signature verification
- Invalid signatures are rejected with 400 Bad Request
- The IPN secret key should never be committed to version control
- Database transactions ensure data consistency and prevent race conditions
Testing
You can test the IPN endpoint using tools like:
- Postman
- curl
- NOWPayments test payments
Example curl command (with test signature):
curl -X POST http://localhost:3000/ipn \
-H "Content-Type: application/json" \
-H "x-nowpayments-sig: your_signature_here" \
-d '{"payment_id":"test123","payment_status":"waiting","price_amount":100,"price_currency":"usd"}'
Documentation
For detailed implementation guide and database schema, refer to the IPN Callback Integration Guide provided with this application.
License
ISC