ZynaPay Payment API
Comprehensive, production-ready payment processing API. Built with modern REST standards, PCI DSS compliance, and real-time 3D Secure authentication built in.
curl -X POST https://app.zynapay.com/api/v1/payment/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"phone": "+19876543210",
"line1": "123 Main St",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US",
"ip_address": "192.168.1.100",
"amount": "100.50",
"currency": "USD",
"card_number": "4242424242424242",
"card_expiry_month": "12",
"card_expiry_year": "2028",
"card_cvv": "123",
"merchant_transaction_id": "ORDER-12345",
"return_url": "https://yoursite.com/return",
"webhook_url": "https://yoursite.com/webhook"
}'
Authentication
All API requests require authentication using your API key. Include your API key in the Authorization header of every request.
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://app.zynapay.com/api/v1/payment/create
Security Best Practices
Important
- Never expose your API key in client-side code
- Use HTTPS for all API requests
- Rotate your API keys regularly
- Monitor API usage for suspicious activity
Quick Start
Get up and running with our API in just a few steps. This guide will walk you through setting up your first payment integration.
Prerequisites
Before you begin, make sure you have:
- A merchant account with ZynaPay
- Basic knowledge of HTTP APIs and JSON
- A development environment for testing
Get Your API Key
Sign up for a merchant account and generate your API key from the dashboard. This key will authenticate all your API requests.
Dashboard Steps:
- Log into your merchant dashboard
- Navigate to Settings → API Keys
- Click Generate New Key
- Copy and securely store your API key
Security Note
Never expose your API key in client-side code or public repositories. Use environment variables to store it securely.
Make Your First Request
Test your API key with a simple authentication request to ensure everything is working correctly.
curl -X POST https://app.zynapay.com/api/v1/payment/status \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"merchant_transaction_id": "YOUR_TRANSACTION_ID"}'
Expected Response
You should receive a 200 OK response with API status information.
Process Your First Payment
Now let's process a real payment using our test environment. Use the test card numbers provided below.
curl -X POST https://app.zynapay.com/api/v1/test/payment/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"phone": "+19876543210",
"line1": "123 Main St",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US",
"ip_address": "192.168.1.100",
"amount": "10.00",
"currency": "USD",
"card_number": "4242424242424242",
"card_expiry_month": "12",
"card_expiry_year": "2028",
"card_cvv": "123",
"merchant_transaction_id": "TEST-12345",
"return_url": "https://yoursite.com/return",
"webhook_url": "https://yoursite.com/webhook"
}'
Test Cards
4242424242424242
4000000000000002
4000000000003220
Test Environment
https://app.zynapay.com/api/v1/test
https://webhook.site/your-url
Handle Responses & Webhooks
Process the API response and set up webhooks to receive real-time payment notifications.
Response Handling
Check the response status and handle accordingly:
success: true- Payment completedrequires_action: true- 3DS needederror- Handle errors gracefully
Webhook Setup
Configure webhooks for real-time updates:
- Set webhook URL in your dashboard
- Verify webhook signatures
- Handle payment status updates
- Test with webhook.site
What's Next?
Now that you've completed the quick start, explore these resources to build your payment integration:
Direct Payment API
Process payments directly through our API with full control over the payment flow. Perfect for custom checkout experiences and server-side payment processing.
Real-time Processing
Instant payment confirmation
Full Control
Customize every aspect of the flow
Secure
End-to-end encryption
3DS Support
Automatic 3D Secure handling
Process a payment directly with card details. If 3D Secure authentication is required, the response will include a redirect URL.
Test Environment
For testing, use: /api/v1/test/payment/create
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| Customer Information | |||
first_name |
string | Yes | Customer's first name |
last_name |
string | Yes | Customer's last name |
email |
string | Yes | Customer's email address |
phone |
string | Yes | Phone number with country code (e.g., +19876543210) |
ip_address |
string | Yes | Customer's IP address |
| Billing Address | |||
line1 |
string | Yes | Address line 1 |
city |
string | Yes | City name |
state |
string | Yes | State/Province code (2 letters, e.g., NY, TX) |
postal_code |
string | Yes | Postal/ZIP code |
country |
string | Yes | Country code (ISO 3166-1 alpha-2, e.g., US) |
| Transaction Details | |||
amount |
string | Yes | Transaction amount (decimal format, e.g., "100.50") |
currency |
string | Yes | Currency code (ISO 4217, e.g., USD, EUR) |
merchant_transaction_id |
string | Yes | Your unique transaction reference |
| Card Information | |||
card_number |
string | Yes | Card number (13-19 digits) |
card_expiry_month |
string | Yes | Expiry month (2 digits, e.g., "01") |
card_expiry_year |
string | Yes | Expiry year (4 digits, e.g., "2028") |
card_cvv |
string | Yes | Card security code (3-4 digits) |
| Callback URLs | |||
return_url |
string | Yes | URL to redirect after payment (for 3DS) |
webhook_url |
string | Yes | URL to receive webhook notifications |
Request Example
{
"first_name": "John",
"last_name": "Doe",
"line1": "123 Main Street",
"country": "US",
"state": "NY",
"city": "New York",
"postal_code": "10001",
"ip_address": "192.168.1.100",
"email": "john.doe@example.com",
"phone": "+19876543210",
"amount": "100.50",
"currency": "USD",
"card_number": "4242424242424242",
"card_expiry_month": "01",
"card_expiry_year": "2028",
"card_cvv": "123",
"return_url": "https://yoursite.com/payment/return",
"webhook_url": "https://yoursite.com/webhooks/payment",
"merchant_transaction_id": "ORDER-12345"
}
Success Response
{
"status": "success",
"reason": "Payment completed",
"merchant_transaction_id": "MRF-3",
"transaction_id": "txn_bY32XICH0yXnp0s7"
}
3DS Response
{
"status": "processing",
"3ds_url": "http://localhost:8002/test-gateway/authentication/eyJpdiI6Ikxmd0ZRTTBQR0pvcGFTSXdsdnRKdXc9PSIsInZhbHVlIjoiQnVVQ1I3S1BKQVlvNGU4YTRmdjhMMFpTRVF5eGwrTEVhMEhRZjE1MHliaz0iLCJtYWMiOiJiZTBiMmQwNTE2NzJjN2MyNTVmNzEwZTNjMTE0MWYzOTBmMTdmNjBhNjFkZTMyNzE0MWVjYTNjY2RhZWExM2EyIiwidGFnIjoiIn0=",
"reason": "Payment authentication reqiured.",
"merchant_transaction_id": "MRF-1",
"transaction_id": "txn_SAx5Fyj4X1pyFIYO"
}
Declined Response
{
"status": "declined",
"reason": "Payment failed.",
"merchant_transaction_id": "MRF-2",
"transaction_id": "txn_KhZl1qEc33JamngW"
}
Validation Failed Response
{
"status": "declined",
"reason": "The first name field is required.",
"merchant_transaction_id": "MRF-141"
}
Response Fields
| Field | Type | Description |
|---|---|---|
status |
string | Transaction status: success, declined, processing |
reason |
string | Human-readable message about the transaction |
transaction_id |
string | Internal transaction ID (unique system identifier) |
merchant_transaction_id |
string | Your unique transaction reference ID |
3ds_url |
string | 3DS authentication URL (only when status is "processing") |
Check the current status of a payment transaction using your merchant transaction ID.
Test Environment
For testing, use: /api/v1/test/payment/status
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
merchant_transaction_id |
string | Yes | Your unique transaction reference |
Request Example
{
"merchant_transaction_id": "ORDER-12345"
}
Success Response
{
"success": true,
"data": {
"transaction_id": "txn_Cf7Gr7PgqIObMAcM",
"merchant_transaction_id": "MRF-123",
"status": "success",
"amount": 10,
"currency": "USD",
"message": "Payment completed.",
"customer_info": {
"phone": "+19876549872",
"line1": "Address",
"postal_code": "12345",
"city": "MINS",
"state": "TX",
"name": "Fname Lname",
"email": "test@gmail.com",
"country": "US"
},
"created_at": "2025-10-06 18:19:53",
"processed_at": "2025-10-06 18:19:53",
"payment_info": {
"card_number": "4242",
"card_expiry_month": 1,
"card_expiry_year": 2028
}
}
}
Error Response
{
"success": false,
"error": "Transaction not found."
}
Transaction Statuses
| Status | Description | Action Required |
|---|---|---|
success |
Payment completed successfully | Complete the order |
declined |
Payment failed, was declined, or validation error occurred | Show error message to user from reason field |
processing |
Payment requires 3D Secure authentication | Redirect user to 3ds_url for authentication |
Common Error Messages
Validation Errors:
- "The first name field is required."
- "The last name field is required."
- "The email must be a valid email address."
- "The card number field is required."
- "The card CVV field is required."
Payment Declined Reasons:
- "Payment failed." (Generic decline)
- "Insufficient funds."
- "Card expired."
- "Invalid card number."
- "CVV verification failed."
Best Practices
- Check the
statusfield in payment creation responses - Display the
reasonfield to users for user-friendly error messages - Handle 3DS redirects when
statusis "processing" - Implement retry logic with the same
merchant_transaction_idto prevent duplicates - Validate input on client-side before sending to API
- Log all errors with transaction IDs for debugging
Hosted Payment API
Create secure, hosted payment pages that handle sensitive card data collection. Perfect for businesses that want to minimize PCI compliance requirements while maintaining full control over customer experience.
PCI Compliant
Minimal PCI scope - card data never touches your server
Secure Card Input
Hosted card input page with 3DS support
Mobile Optimized
Responsive design for all devices
Global Ready
Multi-currency support
How It Works
- Create Payment Request: Send customer and payment details to the Hosted API endpoint
- Receive Card URL: Get a secure
card_urlwhere the customer can enter their card details - Redirect Customer: Redirect the customer to the
card_urlto complete payment - Payment Processing: Customer enters card details, 3DS authentication is handled automatically if required
- Callback: Customer is redirected to your
return_urlafter payment completion - Webhook Notification: Receive payment status via webhook
Create a hosted payment session. Returns a secure card input URL where the customer can enter their card details.
Live Environment
Use this endpoint for production transactions with real cards.
Request Headers
| Header | Value | Required |
|---|---|---|
Authorization |
Bearer YOUR_API_KEY | Yes |
Content-Type |
application/json | Yes |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
first_name |
string | Yes | Customer's first name |
last_name |
string | Yes | Customer's last name |
email |
string | Yes | Customer's email address |
phone |
string | Yes | Customer's phone number (with country code) |
line1 |
string | Yes | Billing address line 1 |
city |
string | Yes | Billing city |
state |
string | Yes | Billing state/province code |
postal_code |
string | Yes | Billing postal/ZIP code |
country |
string | Yes | Two-letter country code (e.g., US, GB) |
ip_address |
string | Yes | Customer's IP address |
amount |
string | Yes | Payment amount (e.g., "10" for $10.00) |
currency |
string | Yes | Three-letter currency code (e.g., USD, INR) |
merchant_transaction_id |
string | Yes | Your unique transaction reference ID |
return_url |
string | Yes | URL to redirect customer after payment |
webhook_url |
string | Yes | URL to receive payment status notifications |
Request Example
curl -X POST https://app.zynapay.com/api/v1/hosted/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Firstname",
"last_name": "Lastname",
"line1": "Address",
"country": "US",
"state": "TX",
"city": "Dallas",
"postal_code": "123456",
"ip_address": "15.6.48.247",
"email": "test@gmail.com",
"phone": "+19876549872",
"amount": "10",
"currency": "USD",
"return_url": "https://yoursite.com/return",
"webhook_url": "https://yoursite.com/webhook",
"merchant_transaction_id": "MRF-156"
}'
Request Body (JSON)
{
"first_name": "Firstname",
"last_name": "Lastname",
"line1": "Address",
"country": "US",
"state": "TX",
"city": "Dallas",
"postal_code": "123456",
"ip_address": "15.6.48.247",
"email": "test@gmail.com",
"phone": "+19876549872",
"amount": "10",
"currency": "USD",
"return_url": "https://yoursite.com/return",
"webhook_url": "https://yoursite.com/webhook",
"merchant_transaction_id": "MRF-156"
}
Success Response
{
"status": "card_url",
"card_url": "https://app.zynapay.com/v1/hosted/card/input/ZEhodVh6Uk5UMXBHTTBKQmRVWjNTSFoyUW1ZPQ==",
"reason": "Redirect to card_url.",
"merchant_transaction_id": "MRF-156",
"transaction_id": "txn_4MOZF3BAuFwHvvBf"
}
Response Fields
| Field | Type | Description |
|---|---|---|
status |
string | Response status. Value: card_url |
card_url |
string | Secure URL where customer enters card details. Redirect customer to this URL. |
reason |
string | Human-readable description of the response |
merchant_transaction_id |
string | Your transaction reference ID (echoed back) |
transaction_id |
string | ZynaPay unique transaction identifier |
Next Step
Redirect your customer to the card_url. After entering their card details and completing any required 3D Secure authentication, they will be redirected to your return_url.
Create a hosted payment session in test mode. Use this endpoint during development and testing.
Test Environment
Use this endpoint for testing with test card numbers. No real payments will be processed.
Request Example
curl -X POST https://app.zynapay.com/api/v1/test/hosted/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Firstname",
"last_name": "Lastname",
"line1": "Address",
"country": "US",
"state": "TX",
"city": "Dallas",
"postal_code": "123456",
"ip_address": "15.6.48.247",
"email": "test@gmail.com",
"phone": "+19876549872",
"amount": "10",
"currency": "USD",
"return_url": "https://yoursite.com/return",
"webhook_url": "https://webhook.site/your-webhook-url",
"merchant_transaction_id": "MRF-150"
}'
The request and response format is identical to the live endpoint. Only the base URL path differs (/api/v1/test/hosted/create).
Test Cards
Use these test card numbers when testing the hosted payment page:
| Card Number | Result | Description |
|---|---|---|
4242424242424242 |
Success | Visa card that will always succeed |
4000000000000002 |
Declined | Card that will always be declined |
4000000000003220 |
3DS Required | Card that requires 3D Secure authentication |
Test Card Details
Any valid future month (e.g., 01)
Any valid future year (e.g., 2028)
Any 3 digits (e.g., 123)
3D Secure Support
The hosted payment page automatically handles 3D Secure (3DS) authentication when required by the card issuer.
3D Secure Flow
- Customer is redirected to the
card_url - Customer enters their card details on the secure hosted page
- If 3DS is required, customer is redirected to their bank's authentication page
- After authentication, the payment is processed
- Customer is redirected to your
return_url - You receive a webhook notification with the payment result
Integration Example (PHP)
public function createHostedPayment(Request $request)
{
$apiKey = config('payment.api_key');
$baseUrl = config('payment.base_url'); // https://app.zynapay.com
$payload = [
'first_name' => $request->first_name,
'last_name' => $request->last_name,
'email' => $request->email,
'phone' => $request->phone,
'line1' => $request->address,
'city' => $request->city,
'state' => $request->state,
'postal_code' => $request->postal_code,
'country' => $request->country,
'ip_address' => $request->ip(),
'amount' => $request->amount,
'currency' => 'USD',
'merchant_transaction_id' => 'ORDER-' . uniqid(),
'return_url' => route('payment.return'),
'webhook_url' => route('payment.webhook'),
];
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $apiKey,
'Content-Type' => 'application/json',
])->post($baseUrl . '/api/v1/hosted/create', $payload);
$data = $response->json();
if ($data['status'] === 'card_url') {
// Store transaction_id for later reference
session(['transaction_id' => $data['transaction_id']]);
// Redirect customer to the hosted card input page
return redirect()->away($data['card_url']);
}
// Handle error
return back()->with('error', $data['reason'] ?? 'Payment initialization failed');
}
Error Responses
| Error Code | HTTP Status | Description |
|---|---|---|
invalid_request |
400 | Missing or invalid request parameters |
invalid_amount |
400 | Amount is invalid or below minimum |
invalid_currency |
400 | Currency code is not supported |
authentication_failed |
401 | Invalid or missing API key |
duplicate_transaction |
409 | merchant_transaction_id already exists |
invalid_webhook_url |
400 | Webhook URL is invalid or unreachable |
invalid_return_url |
400 | Return URL is invalid |
Transaction Status API
Query the current status of any transaction using either your internal transaction ID or your own merchant reference. Useful for reconciliation, order fulfilment checks, and post-3DS status polling.
Flexible Lookup
Query by transaction ID or your own reference
Real-time Status
Always reflects the latest transaction state
Merchant-scoped
Only returns transactions belonging to your account
Full Detail
Includes customer, card, and timing information
Retrieve the current status of a transaction. Supply either transaction_id (the system-generated ID) or
merchant_transaction_id (your own reference). At least one is required.
Test Environment
For testing, use: /api/v1/test/payment/status
Request Headers
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
transaction_id |
string | Conditional | System-generated transaction ID (e.g. txn_Cf7Gr7PgqIObMAcM). Required if merchant_transaction_id is not provided. |
merchant_transaction_id |
string | Conditional | Your own unique transaction reference (3–100 alphanumeric/dash/underscore characters). Required if transaction_id is not provided. |
At Least One Required
You must supply transaction_id, merchant_transaction_id, or both.
Providing both will match on whichever is found first. Omitting both returns a validation error.
Request Example — by merchant_transaction_id
{
"merchant_transaction_id": "ORDER-12345"
}
Request Example — by transaction_id
{
"transaction_id": "txn_Cf7Gr7PgqIObMAcM"
}
cURL Example
curl -X POST https://app.zynapay.com/api/v1/payment/status \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"merchant_transaction_id": "ORDER-12345"}'
Success Response
{
"success": true,
"data": {
"transaction_id": "txn_Cf7Gr7PgqIObMAcM",
"merchant_transaction_id": "ORDER-12345",
"status": "success",
"amount": "100.50",
"currency": "USD",
"message": "Payment completed.",
"customer_info": {
"phone": "+19876543210",
"line1": "123 Main St",
"postal_code": "10001",
"city": "New York",
"state": "NY",
"name": "John Doe",
"email": "john.doe@example.com",
"country": "US"
},
"created_at": "2025-10-06 18:19:53",
"processed_at": "2025-10-06 18:19:53",
"payment_info": {
"card_number": "424242******4242",
"card_expiry_month": "01",
"card_expiry_year": "2028"
}
}
}
Declined / Failed Response
{
"success": true,
"data": {
"transaction_id": "txn_AraABZG7ZMTn9dLx",
"merchant_transaction_id": "ORDER-12346",
"status": "declined",
"amount": "100.50",
"currency": "USD",
"message": "Payment failed.",
"customer_info": {
"phone": "+19876543210",
"line1": "123 Main St",
"postal_code": "10001",
"city": "New York",
"state": "NY",
"name": "John Doe",
"email": "john.doe@example.com",
"country": "US"
},
"created_at": "2025-10-06 18:20:11",
"processed_at": "2025-10-06 18:20:11",
"payment_info": {
"card_number": "424242******4242",
"card_expiry_month": "01",
"card_expiry_year": "2028"
}
}
}
Pending Response
{
"success": true,
"data": {
"transaction_id": "txn_SAx5Fyj4X1pyFIYO",
"merchant_transaction_id": "ORDER-12347",
"status": "processing",
"amount": "100.50",
"currency": "USD",
"message": "Transaction Pending.",
"customer_info": { ... },
"created_at": "2025-10-06 18:21:00",
"processed_at": null,
"payment_info": {
"card_number": "424242******4242",
"card_expiry_month": "01",
"card_expiry_year": "2028"
}
}
}
Error Responses
{
"success": false,
"error": "Transaction not found."
}
{
"success": false,
"error": "Either transaction_id or merchant_transaction_id is required."
}
{
"success": false,
"error": "Unauthorized"
}
Response Fields
| Field | Type | Description |
|---|---|---|
| Top Level | ||
success |
boolean | true when the transaction was found; false on error |
data |
object | Transaction details (present only when success is true) |
error |
string | Human-readable error message (present only when success is false) |
| data object | ||
transaction_id |
string | System-generated transaction identifier |
merchant_transaction_id |
string | Your unique transaction reference |
status |
string | Current transaction status — see status table below |
amount |
string | Transaction amount in decimal format (e.g. "100.50") |
currency |
string | ISO 4217 currency code (e.g. USD) |
message |
string | Human-readable status or decline reason |
customer_info |
object | Customer details: name, email, phone, line1, city, state, postal_code, country |
created_at |
string | Transaction creation timestamp (UTC) |
processed_at |
string / null | Timestamp when the transaction was finalised; null if still pending |
payment_info |
object | Masked card details: card_number, card_expiry_month, card_expiry_year |
Transaction Statuses
| Status | Description | Action Required |
|---|---|---|
success |
Payment completed and funds captured | Fulfil the order |
declined |
Payment was declined or failed | Display the message to the customer and ask them to retry |
processing |
Transaction is awaiting 3DS authentication or a final webhook from the acquirer | Poll again after a short delay or wait for the webhook notification |
refunded |
Payment has been fully refunded | Update your records accordingly |
chargeback |
A chargeback has been raised on this transaction | Contact support to manage the dispute |
Best Practices
- Prefer webhooks over polling — set a
webhook_urlon your payment request to receive instant status updates instead of repeatedly calling this endpoint. - Handle
processinggracefully — if the status isprocessing, wait a few seconds and poll again rather than treating it as a failure. - Store
transaction_idfrom the payment creation response so you can look up the transaction even if your own reference is unavailable. - Use the test endpoint during development:
/api/v1/test/payment/status.
Webhooks
Webhooks notify your system about payment status changes in real-time. Configure your webhook URL in each payment request to receive instant notifications when the payment status changes.
Key Points
- Webhooks are sent to the
webhook_urlspecified in your payment request - Your endpoint must respond with a
200 OKstatus - Process webhooks asynchronously for best performance
- Store the
transaction_idto prevent duplicate processing
Webhook Request Headers
Content-Type: application/json
X-Event-Type: payment.notify
X-Webhook-Timestamp: 1748765432
X-Signature: sha256_hmac_signature (present when API secret is configured)
Webhook Payload
{
"event": "payment.notify",
"data": {
"transaction_id": "txn_Cf7Gr7PgqIObMAcM",
"merchant_transaction_id": "ORDER-123",
"status": "success",
"amount": "50.00",
"currency": "EUR",
"message": "Payment completed.",
"customer_info": {
"phone": "+19876543210",
"line1": "123 Main St",
"postal_code": "10001",
"city": "Berlin",
"state": "Berlin",
"name": "John Doe",
"email": "john@example.com",
"country": "DE"
},
"created_at": "2025-10-06 18:19:53",
"processed_at": "2025-10-06 18:19:53",
"payment_info": {
"card_number": "424242******4242",
"card_expiry_month": "12",
"card_expiry_year": "2028"
}
}
}
{
"event": "payment.notify",
"data": {
"transaction_id": "txn_AraABZG7ZMTn9dLx",
"merchant_transaction_id": "ORDER-124",
"status": "declined",
"amount": "50.00",
"currency": "EUR",
"message": "Transaction failed.",
"customer_info": {
"phone": "+19876543210",
"line1": "123 Main St",
"postal_code": "10001",
"city": "Berlin",
"state": "Berlin",
"name": "John Doe",
"email": "john@example.com",
"country": "DE"
},
"created_at": "2025-10-06 18:20:11",
"processed_at": "2025-10-06 18:20:11",
"payment_info": {
"card_number": "424242******4242",
"card_expiry_month": "12",
"card_expiry_year": "2028"
}
}
}
Webhook Events
| Event Type | Description | When Triggered |
|---|---|---|
payment.notify |
Payment status notification | When payment status changes (success, declined, processing) |
Handling Webhooks (PHP Example)
public function handlePaymentWebhook(Request $request)
{
// Verify signature when present
$signature = $request->header('X-Signature');
$timestamp = $request->header('X-Webhook-Timestamp');
if ($signature) {
if (!$this->verifySignature($request->getContent(), $timestamp, $signature)) {
Log::error('Invalid webhook signature');
return response()->json(['error' => 'Invalid signature'], 401);
}
// Reject requests older than 5 minutes to prevent replay attacks
if (abs(time() - (int) $timestamp) > 300) {
return response()->json(['error' => 'Request expired'], 401);
}
}
// Get webhook data
$event = $request->input('event'); // "payment.notify"
$data = $request->input('data');
// Extract transaction details
$transactionId = $data['transaction_id'];
$merchantTxnId = $data['merchant_transaction_id'];
$status = $data['status']; // "success", "declined", etc.
$message = $data['message'];
// Process based on status
if ($status === 'success') {
// Update order as paid
Log::info("Payment successful: {$merchantTxnId}");
// Your business logic here
} elseif ($status === 'declined') {
// Handle failed payment
Log::warning("Payment declined: {$merchantTxnId} - {$message}");
// Your business logic here
}
// Respond immediately
return response()->json(['received' => true]);
}
private function verifySignature(string $payload, string $timestamp, string $signature): bool
{
$secret = 'your_api_secret'; // from your ZynaPay dashboard
$computed = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
return hash_equals($computed, $signature);
}
Handling Webhooks (Node.js Example)
const express = require('express');
const crypto = require('crypto');
const app = express();
// IMPORTANT: use raw body parser so the signature matches the exact bytes sent
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const rawBody = req.body.toString('utf8');
// Verify signature when present
if (signature) {
if (!verifySignature(rawBody, timestamp, signature)) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// Reject requests older than 5 minutes to prevent replay attacks
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
return res.status(401).json({ error: 'Request expired' });
}
}
const { event, data } = JSON.parse(rawBody);
const { transaction_id, merchant_transaction_id, status, message } = data;
if (status === 'success') {
console.log(`Payment successful: ${merchant_transaction_id}`);
// Your business logic here
} else if (status === 'declined') {
console.warn(`Payment declined: ${merchant_transaction_id} - ${message}`);
// Your business logic here
}
// Respond immediately
res.json({ received: true });
});
function verifySignature(payload, timestamp, signature) {
const secret = 'your_api_secret'; // from your ZynaPay dashboard
const computed = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(signature));
}
app.listen(3000);
Webhook Security
While signature verification is optional, we recommend implementing it to ensure webhook authenticity.
Best Practices
- Always verify the signature if provided
- Store processed transaction IDs to prevent duplicate processing
- Respond with 200 OK immediately, process asynchronously
- Implement retry logic for failed webhook processing
- Use HTTPS endpoints for webhook URLs
- Log all webhook events for debugging
Testing
Use our test environment to develop and test your integration before going live.
Test Cards
| Card Number | Result | Description |
|---|---|---|
4242424242424242 |
Success | Visa card that will always succeed |
4000000000000002 |
Declined | Card that will always be declined |
4000000000003220 |
3DS Required | Card that requires 3D Secure authentication |
Test Environment
Use the test API endpoint:
https://app.zynapay.com/api/v1/test
Payment creation: https://app.zynapay.com/api/v1/test/payment/create
Payment status: https://app.zynapay.com/api/v1/test/payment/status
Error Handling
Handle errors gracefully by checking the response status and error details.
Error Response Format
{
"error": {
"code": "card_declined",
"message": "Your card was declined.",
"type": "card_error",
"decline_code": "insufficient_funds"
}
}
Common Error Codes
| Error Code | HTTP Status | Description |
|---|---|---|
invalid_request |
400 | The request was invalid |
authentication_failed |
401 | Invalid API key |
rate_limit_exceeded |
429 | Too many requests |
server_error |
500 | Internal server error |
Security
We take security seriously and implement industry best practices to protect your data.
PCI Compliance
Our platform is PCI DSS Level 1 compliant, the highest level of certification available in the payments industry.
Data Encryption
- All data is encrypted in transit using TLS 1.3
- Sensitive data is encrypted at rest using AES-256
- API keys are hashed using bcrypt
Fraud Protection
Our advanced fraud detection system analyzes transactions in real-time to identify and prevent fraudulent activity.
Rate Limits
To ensure fair usage and system stability, we implement rate limits on our API endpoints.
Rate Limit Headers
All API responses include rate limit headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
Default Limits
- Authentication endpoints: 10 requests per minute
- Payment endpoints: 100 requests per minute
- Webhook endpoints: 1000 requests per minute
Changelog
Track the latest updates and improvements to our API.
Version 2.0.0 - 2025-01-15
- Added support for 3D Secure 2.0
- Improved webhook reliability
- Enhanced error messages
- New fraud detection features
Version 1.9.0 - 2024-12-01
- Added support for Apple Pay and Google Pay
- Improved rate limiting
- Enhanced documentation