Skip to main content
Before integrating webhooks, make sure you’ve completed the Quickstart guide and have your authentication set up.

Overview

When you make a request to our API, you’ll typically get an immediate response. However, some operations like payments can take time to process. Instead of timing out, we return a pending status and use webhooks to notify you of the final result. You have two options for handling these async operations:
  • Poll the API endpoints periodically (not recommended for production)
  • Use webhooks to receive real-time event updates (recommended)

Webhooks vs Polling

  • Make repeated GET requests to check transaction status - Higher latency and more resource intensive - May miss state changes between polls - Better suited for testing/debugging
  • Receive instant notifications when state changes - More efficient and scalable - No missed events - Recommended for production use

Setting Up Webhooks

1. Create Your Webhook URL

Create a POST endpoint on your server to receive webhook events. The endpoint should:
  1. Accept JSON payloads
  2. Return a 200 OK response
  3. Process events idempotently (handle duplicates safely)
from flask import Flask, request
import hmac
import hashlib
import json

app = Flask(__name__)

def validate_signature(payload, checksum, business_id):
  # Separate event and data
  event = payload['event']
  data = json.dumps(payload['data'])
  message = f"{event}|{data}"

  # Create HMAC SHA256 hash
  expected = hmac.new(
    business_id.encode('utf-8'),
    message.encode('utf-8'),
    hashlib.sha256
  ).hexdigest().upper()

  return hmac.compare_digest(expected, checksum)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
  payload = request.json
  checksum = payload.get('checksum')
  business_id = 'YOUR_BUSINESS_ID'

  # Validate webhook signature
  if not validate_signature(payload, checksum, business_id):
    return 'Invalid signature', 401

  # Process the webhook event
  event_type = payload.get('event')
  if event_type == 'payment.session.succeeded':
    handle_successful_payment(payload['data'])
  elif event_type == 'payment.session.failed':
    handle_failed_payment(payload['data'])

  # Return 200 to acknowledge receipt
  return 'Webhook received', 200

2. Register Your Webhook URL

Add your webhook URL to your account settings:
PATCH /accounts/settings
Request
{
  "webhook_urls": ["https://your-domain.com/webhooks"]
}
Response
{
  "data": {
    "settings": {
      "webhook_urls": ["https://your-domain.com/webhooks"]
    }
  }
}

Security

Security Notice

Important Security Considerations:
  • Never expose sensitive credentials in client-side code or VCS
  • Always validate request signatures and origins
  • Use HTTPS for all API communications
  • Implement proper access control and authentication
  • Follow secure key management practices
Never Share or Expose:
  • API Keys
  • Secret Keys
  • Encryption Keys
  • Webhook Secrets
  • Authentication Tokens
Key Security Measures:
  1. Store sensitive data in secure environment variables or dedicated key management systems
  2. Implement IP whitelisting where possible
  3. Validate all incoming webhook signatures
  4. Use strong TLS/SSL for all connections
  5. Rotate credentials regularly
  6. Log and monitor access attempts
  7. Follow the principle of least privilege
Implementation Tips:
// ❌ Avoid hardcoding secrets
const apiKey = "sk_live_123..."; // Bad practice

// ✅ Use environment variables
const apiKey = process.env.API_KEY; // Good practice

// ✅ Validate webhook signatures
const isValidSignature = verifyWebhookSignature(
  payload,
  signature,
  webhookSecret,
);
For additional security best practices, refer to our Security Guidelines in the documentation.

Verifying Webhook Origins

Secure your webhook endpoint using either or both:

1. Checksum Validation

Each webhook includes a checksum for verification:
{
  "checksum": "32762AE880695AE7343A649CB9C36CA6FF83AA258A139804AEF7D73B421DE097",
  "data": {
    "card_id": "81817411-9ffd-42ba-8bc8-f407b5cef9d9",
    "amount": 1000,
    "reference": "b070b0d2-e394-4783-a6f0-f10ccb3cae89",
    "currency": "USD"
  },
  "event": "card.transaction"
}
To validate:
  1. Concatenate: event|json_encoded_data
  2. Create HMAC SHA-256 hash using your business ID as the key
  3. Compare with the received checksum
The encoded data must exclude the checksum field and be in alphabetical order:
Valid Order
{
  "amount": 1000,
  "card_id": "81817411-9ffd-42ba-8bc8-f407b5cef9d9",
  "currency": "USD",
  "reference": "b070b0d2-e394-4783-a6f0-f10ccb3cae89"
}

2. IP Whitelisting

Whitelist these Juicyway IPs:
157.230.16.106
159.65.123.71
159.89.0.178
161.35.197.34
165.22.94.163
165.227.167.85
206.81.21.212
209.38.209.138
64.226.105.137

Go-Live Checklist

1

Verify Public Access

Ensure your webhook URL is publicly accessible (no localhost)
2

URL Configuration

Add trailing / if using .htaccess
3

Test Integration

Verify JSON parsing and 200 OK responses
4

Handle Long Tasks

Return 200 OK before processing lengthy operations
5

Monitor Failed Webhooks

Track non-200 responses in your logs
6

Implement Idempotency

Handle duplicate events safely

Supported Events

In sandbox, successful transactions remain pending. Only failure events are sent.

Payment Events

{
  "checksum": "",
  "data": {
    "amount": 10000,
    "callback_urls": {},
    "cancellation_reason": null,
    "channel_reference": "",
    "collection_mode": null,
    "correlation_id": "",
    "currency": "NGN",
    "customer": {
      "account_id": "",
      "billing_address": {
        "city": "",
        "country": "NG",
        "line1": "",
        "state": "",
        "zip_code": ""
      },
      "email": "",
      "first_name": "",
      "id": "",
      "last_name": "",
      "phone_number": "",
      "type": ""
    },
    "date": "",
    "description": "",
    "fee": null,
    "id": "",
    "merchant": {
      "address": {
        "city": "",
        "country": "NG",
        "line1": "",
        "line2": null,
        "state": "",
        "zip_code": ""
      },
      "email": "",
      "id": "",
      "mcc": "",
      "name": "",
      "phone": ""
    },
    "order": {
      "identifier": "",
      "items": [
        {
          "name": "Transfer",
          "type": "digital"
        }
      ]
    }
    "mode": "live",
    "order": {
      "identifier": "",
      "items": [
        {
          "name": "Transfer",
          "type": "digital"
        }
      ]
    },
    "payer": {
      "account_name": null,
      "account_number": null,
      "bank_name": ""
    },
    "payment_method": {
      "account_numner": "",
      "account_name": "",
      "bank_code": "",
      "bank_name": "",
      "currency": "",
      "id": "",
      "type": "bank_account"
    },
    "provider_id": "",
    "redirect_url": null,
    "reference": "",
    "status": "success|failed",
    "transaction_id": "",
    "type": "payin|payout"
  },
  "event": "payment.session.succeeded|payment.session.failed"

}

Next Steps