Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.juicyway.com/llms.txt

Use this file to discover all available pages before exploring further.

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:
104.248.130.0
104.248.136.223
142.93.166.85
142.93.170.65
159.65.121.43
165.22.78.88
209.38.217.122
209.38.227.232
46.101.179.170
46.101.190.207
64.226.91.150
167.172.97.202

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