Webhooks Guide

Webhooks push real-time notifications to your server when events occur -- primarily transaction state changes. This is the recommended way to track payments in production.

How It Works

  1. Register a webhook with your callback URL and a secret
  2. When a transaction changes state, Neutron POSTs the event to your URL
  3. Your server verifies the signature and processes the event
  4. Respond with 200 OK to acknowledge receipt

Setup

Register a Webhook

curl -X POST https://api.neutron.me/api/v2/webhook \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{
    "callback": "https://yourapp.com/webhooks/neutron",
    "secret": "your-webhook-secret-key"
  }'

Response:

{
  "id": "01e2a3dc-0c34-4e14-92e4-b270c4778d95",
  "callback": "https://yourapp.com/webhooks/neutron",
  "createdAt": 1747889654376
}

Verify Signatures

Every webhook includes an X-Neutronpay-Signature header. Always verify before processing.

Node.js:

const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());

app.post('/webhooks/neutron', (req, res) => {
  const signature = req.headers['x-neutronpay-signature'];
  const expected = crypto
    .createHmac('sha256', 'your-webhook-secret-key')
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }

  res.status(200).send('OK');

  // Process asynchronously
  switch (req.body.txnState) {
    case 'completed':
      // Payment received -- fulfill the order
      break;
    case 'failed':
      // Payment failed -- notify the customer
      break;
  }
});

Python:

import hmac
import hashlib
from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = 'your-webhook-secret-key'

@app.route('/webhooks/neutron', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Neutronpay-Signature')
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        request.data,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return 'Invalid signature', 401

    event = request.json
    return 'OK', 200

Webhook Payload

{
  "txnId": "5e25d2f4-9bca-4b7a-a1ad-2cf056100cb6",
  "extRefId": "order-12345",
  "txnState": "completed",
  "msg": "",
  "updatedAt": 1676030467492
}

Key States to Handle

StateAction
completedPayment successful -- fulfill the order
failedPayment failed -- notify and retry or refund
expiredInvoice timed out -- prompt customer to retry

Best Practices

Respond quickly. Return 200 OK immediately, then process in a background job.

Be idempotent. You may receive the same event multiple times. Track processed transaction IDs and skip duplicates.

Use HTTPS. Your callback URL must use HTTPS.

Handle retries. If your endpoint is temporarily unavailable, Neutron retries with exponential backoff.

Managing Webhooks

One webhook per account. Use the update endpoint to change URL or secret.

Local Development

Test webhooks locally with a tunnel:

ngrok http 3000
# Then register the ngrok URL as your webhook callback