HajiQ Pay
Developer APIIntegration Guide
Accept payments on your platform with our secure, reliable payment gateway.
https://api.amalq.org/api/v1/hajiq-pay/externalBefore You Start
Essential requirements for API access
What You'll Receive
- API Key
hqp_live_yourproject_xxxxxxxx - Webhook Secret
whsec_xxxxxxxx - Stripe Publishable Key
pk_live_xxxxxxxx
What You Must Provide
You must provide your webhook endpoint URL when requesting API keys. This is where we send payment status notifications.
https://yoursite.com/webhooks/hajiq-payContactsupport@amalq.orgto request your credentials
Architecture
How payments flow through the system
About Frontend Card Collection
For PCI compliance, card details are collected directly via Stripe Elements on your frontend. Your backend creates payment intents through HajiQ Pay, and we handle the Stripe integration for you. You'll receive a Stripe publishable key during onboarding.
Payment Flow
Getting Started
Authenticate your API requests
Authentication
Every request must include your API key in the header:
X-API-Key: hqp_live_xxxAuthorization: Bearer hqp_live_xxxAmount Format
Amounts are in cents. $50.00 = 5000
Currencies
API Reference
Click any endpoint to see request and response details
Code Examples
Production-ready integration snippets
Backend: Create Payment Intent
1// Create a payment intent on your server2const response = await fetch('https://api.amalq.org/api/v1/hajiq-pay/external/payment-intents', {3 method: 'POST',4 headers: {5 'X-API-Key': process.env.HAJIQ_PAY_API_KEY,6 'Content-Type': 'application/json',7 },8 body: JSON.stringify({9 amount: 5000, // $50.00 in cents10 currency: 'usd',11 externalReferenceId: 'ORDER-12345',12 customerEmail: 'customer@example.com',13 customerName: 'John Doe',14 description: 'Purchase from My Store',15 }),16});1718const { data } = await response.json();1920// Return clientSecret to your frontend21return { clientSecret: data.clientSecret };Frontend: Stripe Elements Integration
Note: Use @stripe/stripe-js and @stripe/react-stripe-js for React projects. The clientSecret from HajiQ Pay works directly with Stripe's payment confirmation.
1// Frontend: Use Stripe Elements for PCI-compliant card collection2// The clientSecret from HajiQ Pay works directly with Stripe Elements3import { loadStripe } from '@stripe/stripe-js';4import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';56// Initialize Stripe with YOUR Stripe publishable key7// (provided by HajiQ Pay during onboarding)8const stripePromise = loadStripe('pk_live_your_publishable_key');910function CheckoutForm() {11 const stripe = useStripe();12 const elements = useElements();1314 const handleSubmit = async (e) => {15 e.preventDefault();1617 // 1. Get clientSecret from YOUR backend (which calls HajiQ Pay)18 const { clientSecret } = await fetch('/api/create-payment', {19 method: 'POST',20 headers: { 'Content-Type': 'application/json' },21 body: JSON.stringify({ orderId: 'ORDER-12345', amount: 5000 }),22 }).then(res => res.json());2324 // 2. Confirm payment with Stripe Elements25 const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {26 payment_method: {27 card: elements.getElement(CardElement),28 billing_details: { name: 'John Doe' },29 },30 });3132 if (error) {33 console.error(error.message);34 } else if (paymentIntent.status === 'succeeded') {35 // 3. HajiQ Pay receives webhook, notifies YOUR server36 window.location.href = '/order-confirmed';37 }38 };3940 return (41 <form onSubmit={handleSubmit}>42 <CardElement options={{ style: { base: { fontSize: '16px' } } }} />43 <button type="submit">Pay Now</button>44 </form>45 );46}4748// Wrap your checkout with Stripe Elements provider49export default function Checkout() {50 return (51 <Elements stripe={stripePromise}>52 <CheckoutForm />53 </Elements>54 );55}Payment Statuses
Track payments through their lifecycle
pendingWaiting for customer
processingCard submitted
succeededPayment complete
failedCard declined
cancelledCancelled by you
refundedMoney returned
Webhooks
Receive real-time payment notifications
payment.succeededPayment completed successfully – fulfill the order!
payment.failedCard declined or error occurred
payment.cancelledPayment was cancelled
Webhook Payload Example
{ "id": "evt_xxxxxxxxxxxx", "type": "payment.succeeded", "created": 1703244600, "data": { "object": { "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "status": "succeeded", "amount": 5000, "currency": "USD", "externalReferenceId": "ORDER-12345", "customerEmail": "customer@example.com", "customerName": "John Doe", "completedAt": "2025-12-22T10:35:00.000Z" } }}Webhook Headers
X-HajiQ-SignatureSignature to verify authenticityX-HajiQ-Delivery-IDUnique delivery IDX-HajiQ-Event-TypeEvent typeContent-Typeapplication/jsonWebhook Verification
1const crypto = require('crypto');23function verifyWebhook(payload, signature, secret) {4 // Parse the signature header5 const parts = signature.split(',');6 let timestamp, sig;78 for (const part of parts) {9 const [key, value] = part.split('=');10 if (key === 't') timestamp = value;11 if (key === 'v1') sig = value;12 }1314 // Check timestamp is recent (within 5 minutes)15 const now = Math.floor(Date.now() / 1000);16 if (Math.abs(now - parseInt(timestamp)) > 300) {17 return false; // Too old, possible replay attack18 }1920 // Calculate expected signature21 const signedPayload = `${timestamp}.${payload}`;22 const expectedSig = crypto23 .createHmac('sha256', secret)24 .update(signedPayload)25 .digest('hex');2627 // Compare signatures (use timing-safe comparison)28 return crypto.timingSafeEqual(29 Buffer.from(sig),30 Buffer.from(expectedSig)31 );32}3334// In your webhook endpoint35app.post('/webhooks/hajiq-pay', (req, res) => {36 const signature = req.headers['x-hajiq-signature'];37 const payload = JSON.stringify(req.body);3839 if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {40 return res.status(400).send('Invalid signature');41 }4243 const event = req.body;4445 switch (event.type) {46 case 'payment.succeeded':47 const orderId = event.data.object.externalReferenceId;48 fulfillOrder(orderId);49 break;50 case 'payment.failed':51 // Handle failed payment52 break;53 }5455 res.status(200).send('OK');56});Retry Schedule
If your server doesn't respond with 200, we retry:
After 6 failed attempts, we stop trying.
Security
Keep your integration secure
Card Data Isolation
Card numbers go directly to Stripe via Elements. Never touches your servers.
Webhook Verification
Always verify X-HajiQ-Signature using HMAC-SHA256.
API Key Security
Never expose your API key in frontend code. Use environment variables.
Replay Protection
Reject webhook timestamps older than 5 minutes.
Rate Limits
If you hit the limit, you'll receive a 429 Rate Limited response.
Error Codes
Handle errors gracefully
Error Response Format
{
"success": false,
"error": { "code": "ERROR_CODE", "message": "..." }
}| Code | HTTP | Meaning |
|---|---|---|
MISSING_API_KEY | 401 | No API key provided |
INVALID_API_KEY | 401 | API key is wrong |
API_KEY_EXPIRED | 401 | API key has expired |
API_KEY_REVOKED | 401 | API key was revoked |
INSUFFICIENT_SCOPE | 403 | API key doesn't have permission |
RATE_LIMITED | 429 | Too many requests |
VALIDATION_ERROR | 400 | Request data is invalid |
INVALID_AMOUNT | 400 | Amount is out of range |
UNSUPPORTED_CURRENCY | 400 | Currency not supported |
PAYMENT_NOT_FOUND | 404 | Payment doesn't exist |
PAYMENT_CANNOT_BE_CANCELLED | 400 | Payment already completed |
PAYMENT_NOT_REFUNDABLE | 400 | Payment can't be refunded |
INVALID_REFUND_AMOUNT | 400 | Refund amount is wrong |
REFUND_NOT_FOUND | 404 | Refund doesn't exist |
INTERNAL_ERROR | 500 | Something went wrong on our end |