Skip to navigation

Card payments

Hosted checkout, portal redirects, and status polling
View as Markdown

Collect card payments through SenjaroPay’s hosted checkout. Configure where the customer lands after checkout in the merchant portal, then drive the payment from your backend: create the session, open checkout_url, and poll status until the result is final.

All calls are POST to https://api.senjaropay.com. Send Content-Type: application/json, x-api-key, and x-api-secret.

Send x-idempotency-key only on POST /senjaropay/merchant/card/pay. Status does not use it.

Configure redirect URLs

Success, fail, and cancel destinations are set in the merchant portal, not in the pay request body.

  1. Open the merchant portal.
  2. Go to Developers.
  3. Open Redirect URLs.
  4. Save three public HTTPS URLs on your domain:
FieldExample
Success URLhttps://<your-domain>/success
Fail URLhttps://<your-domain>/fail
Cancel URLhttps://<your-domain>/cancel

After checkout, SenjaroPay redirects the browser to the matching page. Your server should not treat that redirect as the source of truth for fulfillment — confirm with status or a webhook.

How it works

1

Create the payment

Call POST /senjaropay/merchant/card/pay. Store data.reference and data.checkout_url.

2

Open checkout

Send the customer to checkout_url so they can enter card details on the hosted page.

3

Poll until final

Call POST /senjaropay/merchant/card/status with that reference until data.status is final (success, fail, cancelled, or expired).

4

Customer return

SenjaroPay redirects the browser to the Success, Fail, or Cancel URL you saved in the portal. Use polling (or webhooks) for fulfillment; use the redirect only to show the customer the right page.

Polling tells your backend how the payment ended. The redirect is SenjaroPay returning the customer to your site.

Authentication

HeaderPayStatusDescription
Content-TypeYesYesapplication/json
x-api-keyYesYesDashboard Public Key (Authentication)
x-api-secretYesYesSecret key. Server-side only.
x-idempotency-keyYesNoA new UUID for each card payment
import { randomUUID } from "node:crypto";
const idempotencyKey = randomUUID();
import uuid
idempotency_key = str(uuid.uuid4())

Reuse a key only when retrying the same card payment (same amount, customer, and webhook). A repeated key returns the original result and does not open a second checkout.

Create a card payment

POST https://api.senjaropay.com/senjaropay/merchant/card/pay

Creates a pending payment and returns checkout_url. Redirect the customer there. Persist data.reference for status checks and webhooks.

The session stops accepting payment at data.expires_at.

Request

{
"payment_type": "card",
"details": {
"amount": 1000,
"currency": "TZS"
},
"customer": {
"firstname": "John",
"lastname": "Customer",
"phone_number": "255740000001",
"email": "test.customer@example.com",
"country": "Tanzania",
"city": "Dar es Salaam",
"address": "Sample Street"
},
"submerchant_id": "",
"webhook_url": "https://example.com/webhooks/senjaropay/mock-callback"
}
curl -sS -X POST "https://api.senjaropay.com/senjaropay/merchant/card/pay" \
-H "Content-Type: application/json" \
-H "x-api-key: ${SENJARO_API_KEY}" \
-H "x-api-secret: ${SENJARO_API_SECRET}" \
-H "x-idempotency-key: $(uuidgen)" \
-d '{
"payment_type": "card",
"details": { "amount": 1000, "currency": "TZS" },
"customer": {
"firstname": "John",
"lastname": "Customer",
"phone_number": "255740000001",
"email": "test.customer@example.com",
"country": "Tanzania",
"city": "Dar es Salaam",
"address": "Sample Street"
},
"submerchant_id": "",
"webhook_url": "https://example.com/webhooks/senjaropay/mock-callback"
}'

Response

{
"status": "success",
"code": 200,
"data": {
"amount": {
"currency": "TZS",
"value": 1000
},
"expires_at": "2026-09-28T09:30:16.066Z",
"object": "payment",
"payment_type": "card",
"reference": "CARD-CK7EVXQVK7CP",
"status": "pending",
"checkout_url": "https://cardpay.com/MI/payment.html?uuid=GgffdGF52eg6gA2AFd64Dchf"
}
}

data.status is pending until checkout finishes. Do not fulfill on this response.

Request fields

FieldTypeRequiredDescription
payment_typestringYesMust be card
details.amountintegerYesAmount to collect, in major units
details.currencystringYesTZS
customer.firstnamestringYesCustomer first name
customer.lastnamestringYesCustomer last name
customer.phone_numberstringYesMSISDN, digits only, no + (example 255740000001)
customer.emailstringYesCustomer email
customer.countrystringYesBilling country
customer.citystringYesBilling city
customer.addressstringYesBilling address
submerchant_idstringNoSub-merchant ID from the dashboard. See Introduction. Send "" when unused
webhook_urlstringNoHTTPS URL that receives the payment result

Redirect URLs are not fields on this request. Configure them in the merchant portal.

Check status

Use status as your primary reconciliation path while the customer is on checkout, and whenever a webhook is late, dropped, or fails verification. This call reads the payment. It does not create a charge.

POST https://api.senjaropay.com/senjaropay/merchant/card/status

Pass the reference returned by card pay. Authenticate with x-api-key and x-api-secret only.

Request

{
"reference": "CARD-8LBDCZ6Q6XZS"
}
curl -sS -X POST "https://api.senjaropay.com/senjaropay/merchant/card/status" \
-H "Content-Type: application/json" \
-H "x-api-key: ${SENJARO_API_KEY}" \
-H "x-api-secret: ${SENJARO_API_SECRET}" \
-d '{"reference":"CARD-8LBDCZ6Q6XZS"}'

Response

{
"status": "success",
"code": 200,
"data": {
"object": "payment",
"payment_type": "card",
"reference": "CARD-8LBDCZ6Q6XZS",
"status": "pending",
"amount": {
"currency": "TZS",
"value": 1000
},
"provider_status": null,
"created_at": "2026-09-28T07:39:17.000Z",
"updated_at": "2026-09-28T07:39:17.000Z"
}
}
FieldDescription
data.referenceSame reference you stored at checkout creation
data.statusCurrent state. Keep polling while pending; stop on success, fail, cancelled, or expired
data.amountCurrency and value recorded for the payment
data.provider_statusProcessor status. null until the card processor reports a result
data.created_atWhen the payment was created
data.updated_atWhen the payment record last changed

Poll on a short interval (for example every 3 seconds) until data.status is final or expires_at from the create response has passed. Make fulfillment idempotent on reference so a late webhook and a status check cannot both fulfill the same order.

Errors

HTTP statusTypical error_codeWhen
400validation_errorMissing field, invalid amount, or malformed JSON
401unauthorizedMissing or invalid x-api-key or x-api-secret
{
"status": "error",
"code": 400,
"error_code": "validation_error",
"message": "reference is required"
}

Examples on this page are mock values. Keep API keys in server-side secrets.