Card payments

Hosted checkout and status reconciliation
View as Markdown

Collect a card payment by opening a hosted checkout, then confirm the result with a webhook. If the webhook does not arrive, read the payment with the status endpoint.

Both 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. The status call does not use it.

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

Generate the idempotency key in your backend when you create a payment. Do not reuse a key across different payments.

import { randomUUID } from "node:crypto";
const idempotencyKey = randomUUID();
import uuid
idempotency_key = str(uuid.uuid4())

Reuse a key only when you are retrying the same card payment request (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

SenjaroPay creates a pending payment and returns checkout_url. Redirect the customer to that URL to enter card details. Store data.reference. You will need it for webhooks and for status checks.

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"
},
"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"
},
"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 the customer finishes checkout. Do not fulfill the order 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
webhook_urlstringYesHTTPS URL that receives the payment result

Check status

Use this when the webhook is late, dropped, or fails your signature check. It reads the payment. It does not create a new 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 SenjaroPay payment state. pending means the result is not final
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 with backoff, and stop when data.status is no longer pending or when 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.