Skip to content

Quick Start

Accept your first payment in under 5 minutes.

Prerequisites

  • A Salam Gateway account (Sign up)
  • Your API keys from the dashboard

Step 1: Install the SDK

bash
npm install @salamgateway/node
bash
yarn add @salamgateway/node
bash
pnpm add @salamgateway/node

Step 2: Create a Payment

typescript
import Salam from '@salamgateway/node';

const salam = new Salam({
  apiKey: 'sk_test_your_api_key',
  sandbox: true, // Use sandbox for testing
});

// Create a payment
const payment = await salam.payments.create({
  amount: 10000, // RM 100.00 in cents
  description: 'Premium subscription',
  success_url: 'https://yoursite.com/success',
  cancel_url: 'https://yoursite.com/cancel',
});

// Redirect customer to checkout
console.log(payment.redirect_url);
// https://sandbox-checkout.salamgateway.com/pay/pay_xxx

Step 3: Handle Webhooks

typescript
import express from 'express';

const app = express();

app.post('/webhooks/salam', 
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.headers['salam-signature'];
    
    try {
      const event = salam.webhooks.constructEvent(req.body, sig);
      
      if (event.type === 'payment.captured') {
        const payment = event.data;
        // Fulfill the order
        console.log('Payment successful:', payment.id);
      }
      
      res.json({ received: true });
    } catch (err) {
      res.status(400).send(`Webhook Error: ${err.message}`);
    }
  }
);

Step 4: Test the Integration

Use these test credentials in sandbox mode:

BankAccount
MaybankAny 12-digit number
CIMBAny 10-digit number

TIP

All payments in sandbox mode will succeed automatically.

Complete Example

typescript
import express from 'express';
import Salam from '@salamgateway/node';

const app = express();
const salam = new Salam({
  apiKey: process.env.SALAM_API_KEY,
  sandbox: true,
});

// Create payment endpoint
app.post('/create-payment', async (req, res) => {
  try {
    const payment = await salam.payments.create({
      amount: req.body.amount,
      description: req.body.description,
      success_url: `${req.body.base_url}/success`,
      cancel_url: `${req.body.base_url}/cancel`,
    });
    
    res.json({ redirect_url: payment.redirect_url });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Webhook endpoint
app.post('/webhooks/salam',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['salam-signature'];
    
    try {
      const event = salam.webhooks.constructEvent(req.body, signature);
      
      switch (event.type) {
        case 'payment.captured':
          // Payment successful
          console.log('Payment captured:', event.data.id);
          break;
        case 'payment.failed':
          // Payment failed
          console.log('Payment failed:', event.data.id);
          break;
      }
      
      res.json({ received: true });
    } catch (error) {
      res.status(400).send(`Webhook Error: ${error.message}`);
    }
  }
);

app.listen(3000);

Next Steps

Get started with Salam Payments in under 5 minutes. This guide will walk you through creating your first payment.

1. Get Your API Keys

Sign up at dashboard.salam.com and get your API keys from the Developers section.

You'll have two sets of keys:

  • Test keys - Start with sk_test_ and pk_test_ for development
  • Live keys - Start with sk_live_ and pk_live_ for production

Keep your secret key safe

Never expose your secret key in client-side code or commit it to version control. Use environment variables to store your keys securely.

2. Install the SDK

Choose your preferred package manager:

bash
npm install @salam/salam-node
bash
yarn add @salam/salam-node
bash
pnpm add @salam/salam-node

For browser-side integration:

html
<script src="https://js.salam.com/v1/salam.js"></script>

3. Create Your First Payment

Server-side (Node.js)

javascript
const Salam = require('@salam/salam-node');
const salam = new Salam('sk_test_xxxxx');

// Create a payment
const payment = await salam.payments.create({
  amount: 5000, // RM 50.00 (amount in cents)
  currency: 'MYR',
  payment_method_data: {
    type: 'card',
    card: {
      number: '4242424242424242',
      exp_month: 12,
      exp_year: 2025,
      cvc: '123',
    },
  },
  description: 'Test payment',
  metadata: {
    order_id: '12345',
  },
});

console.log(payment.id); // pay_xxxxx
console.log(payment.status); // succeeded

Client-side (Browser)

Create a payment form with secure card input:

html
<!DOCTYPE html>
<html>
<head>
  <title>Salam Payment</title>
  <script src="https://js.salam.com/v1/salam.js"></script>
  <style>
    #card-element {
      border: 1px solid #ccc;
      padding: 10px;
      border-radius: 4px;
    }
    .error {
      color: #f93e3e;
      margin-top: 8px;
    }
  </style>
</head>
<body>
  <form id="payment-form">
    <div id="card-element"></div>
    <div id="card-errors" class="error"></div>
    <button type="submit">Pay RM 50.00</button>
  </form>

  <script>
    const salam = Salam('pk_test_xxxxx');
    const elements = salam.elements();
    const cardElement = elements.create('card');
    cardElement.mount('#card-element');

    // Handle real-time validation errors
    cardElement.on('change', (event) => {
      const displayError = document.getElementById('card-errors');
      if (event.error) {
        displayError.textContent = event.error.message;
      } else {
        displayError.textContent = '';
      }
    });

    // Handle form submission
    document.getElementById('payment-form').addEventListener('submit', async (e) => {
      e.preventDefault();
      
      const { paymentMethod, error } = await salam.createPaymentMethod({
        type: 'card',
        card: cardElement,
      });
      
      if (error) {
        console.error(error.message);
        return;
      }
      
      // Send paymentMethod.id to your server
      const response = await fetch('/create-payment', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          payment_method: paymentMethod.id,
          amount: 5000,
        }),
      });
      
      const result = await response.json();
      
      if (result.status === 'succeeded') {
        alert('Payment successful!');
      } else if (result.status === 'requires_action') {
        // Handle 3D Secure
        const { error: confirmError } = await salam.confirmPayment(
          result.client_secret
        );
        if (confirmError) {
          console.error(confirmError);
        } else {
          alert('Payment successful!');
        }
      }
    });
  </script>
</body>
</html>

Server endpoint for browser payments

javascript
const express = require('express');
const app = express();
const Salam = require('@salam/salam-node');
const salam = new Salam(process.env.SALAM_SECRET_KEY);

app.use(express.json());

app.post('/create-payment', async (req, res) => {
  try {
    const { payment_method, amount } = req.body;
    
    const payment = await salam.payments.create({
      amount,
      currency: 'MYR',
      payment_method,
      description: 'Order payment',
    });
    
    res.json({
      status: payment.status,
      client_secret: payment.client_secret,
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

4. Handle Webhooks

Set up webhooks to receive real-time payment notifications:

javascript
const express = require('express');
const app = express();

// Important: Use raw body for webhook verification
app.post('/webhook', 
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.headers['x-salam-signature'];
    const webhookSecret = 'whsec_xxxxx';
    
    try {
      const event = salam.webhooks.constructEvent(
        req.body,
        sig,
        webhookSecret
      );
      
      switch (event.type) {
        case 'payment.succeeded':
          const payment = event.data.object;
          console.log(`Payment ${payment.id} succeeded!`);
          // Fulfill the order
          fulfillOrder(payment);
          break;
          
        case 'payment.failed':
          console.log(`Payment ${event.data.object.id} failed`);
          // Handle failed payment
          break;
          
        case 'refund.created':
          console.log(`Refund ${event.data.object.id} created`);
          // Handle refund
          break;
      }
      
      res.json({ received: true });
    } catch (err) {
      console.error('Webhook signature verification failed:', err.message);
      res.status(400).send(`Webhook Error: ${err.message}`);
    }
  }
);

function fulfillOrder(payment) {
  // Your order fulfillment logic
  console.log('Fulfilling order for payment:', payment.id);
}

Webhook Secret

Get your webhook signing secret from Dashboard → Developers → Webhooks. Each endpoint has a unique secret starting with whsec_.

5. FPX Payments (Malaysian Banks)

To accept FPX payments:

javascript
// Server-side
const payment = await salam.payments.create({
  amount: 10000, // RM 100.00
  currency: 'MYR',
  payment_method_data: {
    type: 'fpx',
    fpx: {
      bank_code: 'MBBEMYKL', // Maybank
    },
  },
  return_url: 'https://yoursite.com/payment/complete',
});

// Redirect customer to payment.next_action.redirect_to_url
res.redirect(payment.next_action.redirect_to_url);

Supported FPX banks:

  • Maybank (MBBEMYKL)
  • CIMB Bank (CIBBMYKL)
  • Public Bank (PBBEMYKL)
  • Hong Leong Bank (HLBBMYKL)
  • RHB Bank (RHBBMYKL)
  • And more...

See full list of FPX banks →

Next Steps

Now that you've created your first payment, learn more about:

Common Issues

Payment fails with "card_declined"

This is normal in test mode. Use test card 4242 4242 4242 4242 for successful payments, or see Test Mode for other test cards.

Webhook signature verification fails

Make sure you're using the raw request body (Buffer or string), not a parsed JSON object. See Webhook Signatures.

CORS errors in browser

Make sure you're using your publishable key (pk_test_ or pk_live_) in client-side code, not your secret key.

Released under the MIT License.