API Reference

πŸ”” Handling JamsrPay Webhooks

Webhooks allow JamsrPay to notify your application when a payment status changes (e.g., pending β†’ success).

Every time a payment is updated, JamsrPay will send a POST request to the webhook URL you set in your dashboard.

πŸ“˜

You can also check our Prebuilt Webhook Examples on GitHub

πŸ“¦ Example Webhook Payload

{
  "input_amount": "100",
  "input_currency": "USD",
  "invoice_id": "inv_12345",
  "paid_amount": "100",
  "payment_currency": "TRX",
  "status": "Settled",
  "merchant_order_id": "ORD-98765"
}

πŸ“Œ Important Headers

HeaderDescription
x-jamsrpay-signatureHMAC SHA256 signature of the payload (used to verify authenticity).
x-jamsrpay-timestampTimestamp when webhook was generated (helps prevent replay attacks).

βœ… Steps to Handle a Webhook

  1. Receive the webhook
    Your server should expose a POST endpoint (e.g., /api/webhook/jamsrpay).
  2. Verify the signature
    Use the x-jamsrpay-signature header and your WEBHOOK_SECRET to compute your own signature and compare. If they don’t match β†’ reject the request.
    const crypto = require("crypto");
    
    function verifySignature(payload, signature, secret) {
      const computed = crypto
        .createHmac("sha256", secret)
        .update(JSON.stringify(payload))
        .digest("hex");
      return computed === signature;
    }
    
  3. Validate the payload
    • Ensure input_amount matches your order amount.
    • Ensure input_currency matches what you expected.
    • Check merchant_order_id matches an existing order in your DB.
  4. Update your database
    • If status === "Settled" β†’ mark the order as PAID / COMPLETED.
    • If status === "Failed" or status === "Expired" β†’ mark as FAILED.
  5. Respond quickly
    Always return 200 OK after processing. JamsrPay will retry if your server is down or takes too long.

πŸ” Best Practices

  • Store your WEBHOOK_SECRET securely in environment variables.
  • Always verify signature before trusting the webhook.
  • Respond within success status to avoid retries.
  • Keep webhook handling idempotent (safe to run multiple times).

πŸš€ Webhook Example

This example shows how to handle JamsrPay webhooks .

import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

// Your webhook secret from JamsrPay Dashboard
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

/**
 * Verify JamsrPay signature
 */
function verifySignature(payload, signature, secret) {
  const computed = crypto
    .createHmac("sha256", secret)
    .update(JSON.stringify(payload))
    .digest("hex");

  return computed === signature;
}

app.post("/webhook/jamsrpay", async (req, res) => {
  try {
    const signature = req.headers["x-jamsrpay-signature"];
    const timestamp = req.headers["x-jamsrpay-timestamp"];
    const payload = req.body;

    // 1. Ensure secret is configured
    if (!WEBHOOK_SECRET) {
      return res.status(500).json({ message: "WEBHOOK_SECRET not configured" });
    }

    // 2. Verify signature
    if (!signature || !verifySignature(payload, signature, WEBHOOK_SECRET)) {
      return res.status(401).json({ message: "Invalid signature" });
    }

    // 3. Extract relevant fields
    const { input_amount, input_currency, merchant_order_id, status } = payload;

    // 4. Validate order in your database (pseudo-code)
    const order = await db.orders.findOne({ id: merchant_order_id });
    if (!order) {
      return res.status(404).json({ message: "Order not found" });
    }

    if (Number(order.amount) !== Number(input_amount) || input_currency !== "USD") {
      return res.status(400).json({ message: "Invalid order details" });
    }

    // 5. Update order status
    if (status === "Settled") {
      await db.orders.update(
        { id: merchant_order_id },
        { status: "COMPLETED" }
      );
    }

    // 6. Respond quickly (200 OK)
    return res.json({ message: "Webhook processed successfully" });
  } catch (err) {
    console.error("Webhook Error:", err);
    return res.status(500).json({ message: "Internal Server Error" });
  }
});

app.listen(3000, () => {
  console.log("βœ… Listening for JamsrPay webhooks on http://localhost:3000");
});
import express, { Request, Response } from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

// Your webhook secret from JamsrPay Dashboard
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET as string;

/**
 * Type definition for webhook payload
 */
interface JamsrPayWebhookPayload {
  input_amount: string;
  input_currency: string;
  invoice_id: string;
  paid_amount: string;
  payment_currency: string;
  status: "Settled" | "Failed" | "Expired" | string;
  merchant_order_id: string;
}

/**
 * Verify JamsrPay signature
 */
function verifySignature(payload: object, signature: string, secret: string): boolean {
  const computed = crypto
    .createHmac("sha256", secret)
    .update(JSON.stringify(payload))
    .digest("hex");

  return computed === signature;
}

app.post("/webhook/jamsrpay", async (req: Request, res: Response) => {
  try {
    const signature = req.headers["x-jamsrpay-signature"] as string | undefined;
    const timestamp = req.headers["x-jamsrpay-timestamp"] as string | undefined;
    const payload = req.body as JamsrPayWebhookPayload;

    // 1. Ensure secret is configured
    if (!WEBHOOK_SECRET) {
      return res.status(500).json({ message: "WEBHOOK_SECRET not configured" });
    }

    // 2. Verify signature
    if (!signature || !verifySignature(payload, signature, WEBHOOK_SECRET)) {
      return res.status(401).json({ message: "Invalid signature" });
    }

    // 3. Extract relevant fields
    const { input_amount, input_currency, merchant_order_id, status } = payload;

    // 4. Validate order in your database (pseudo-code)
    const order = await db.orders.findOne({ id: merchant_order_id }); // Replace with your DB query
    if (!order) {
      return res.status(404).json({ message: "Order not found" });
    }

    if (Number(order.amount) !== Number(input_amount) || input_currency !== "USD") {
      return res.status(400).json({ message: "Invalid order details" });
    }

    // 5. Update order status
    if (status === "Settled") {
      await db.orders.update(
        { id: merchant_order_id },
        { status: "COMPLETED" }
      );
    }

    // 6. Respond quickly (200 OK)
    return res.json({ message: "Webhook processed successfully" });
  } catch (err) {
    console.error("Webhook Error:", err);
    return res.status(500).json({ message: "Internal Server Error" });
  }
});

app.listen(3000, () => {
  console.log("βœ… Listening for JamsrPay webhooks on http://localhost:3000");
});
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::post('/webhook', function (Request $request) {
    $payload = $request->getContent();
    $signature = $request->header('x-jamsrpay-signature');
    $secret = env('JAMSRPAY_SECRET');

    // Verify HMAC-SHA256
    $expectedSignature = hash_hmac('sha256', $payload, $secret);

    if (!hash_equals($expectedSignature, $signature)) {
        return response('Invalid signature', 400);
    }

    $event = $request->input('event');
    $data = $request->input('data');

    if ($event === 'payment.finished') {
        \Log::info('βœ… Payment confirmed', $data);
        // Update order status
    }

    return response('Webhook received', 200);
});
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"io/ioutil"
	"net/http"

	"github.com/gin-gonic/gin"
)

var jamsrPaySecret = "your_jamsrpay_secret"

type WebhookPayload struct {
	Event     string                 `json:"event"`
	Data      map[string]interface{} `json:"data"`
	Signature string                 `json:"signature"`
}

func verifySignature(payload []byte, signature string) bool {
	h := hmac.New(sha256.New, []byte(jamsrPaySecret))
	h.Write(payload)
	expected := hex.EncodeToString(h.Sum(nil))
	return hmac.Equal([]byte(expected), []byte(signature))
}

func main() {
	r := gin.Default()
	r.POST("/webhook", func(c *gin.Context) {
		body, _ := ioutil.ReadAll(c.Request.Body)

		var payload WebhookPayload
		json.Unmarshal(body, &payload)

		signature := c.GetHeader("x-jamsrpay-signature")

		if !verifySignature(body, signature) {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid signature"})
			return
		}

		if payload.Event == "payment.finished" {
			// Process payment
			c.JSON(http.StatusOK, gin.H{"status": "Payment processed"})
			return
		}

		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})
	r.Run(":3000")
}
πŸ“˜

πŸ‘‰ Full working examples are available in our