Skip to main content

Callbacks (notifications)

When a transaction reaches a final status, MoyaPay sends a POST request to your server.

Where are they sent?​

To the callback URLs registered on your account by MoyaPay:

  • one URL for payins;
  • one URL for payouts.

To create or change them, contact your MoyaPay representative.

Notification content​

Headers:

HeaderValue
Content-Typeapplication/json
X-SignatureHMAC-SHA256 signature of the body, hex-encoded

Body:

{
"code": 200,
"status": "SUCCESS",
"message": "Your transaction is SUCCESS",
"transactionID": "ORDER-2026-000123",
"amount": "1000",
"date": "2026-10-04T10:15:30.000Z",
"motif": ""
}
FieldDescription
statusFinal status: SUCCESS or FAILED
transactionIDYour merchant_transaction_id
amountTransaction amount, as a string
dateCreation date of the transaction (ISO 8601)
motifFailure reason when the operator provides one, otherwise an empty string
code, messageInformational values. Do not base your logic on them

Verify the signature​

Compute the HMAC-SHA256 of the request's raw body with your signing secret, then compare it with X-Signature. Reject the notification if they do not match.

Use the raw body

The signature covers the exact bytes sent by MoyaPay. If you parse the JSON and re-encode it before computing the signature, field order or formatting may change and the check will fail. Compute it on the body as received.

const crypto = require('crypto');
const express = require('express');

const app = express();

app.post(
'/api/moyapay/notify',
express.raw({ type: 'application/json' }), // raw body (Buffer)
(req, res) => {
const received = req.get('X-Signature') || '';
const expected = crypto
.createHmac('sha256', process.env.MOYAPAY_CALLBACK_SECRET)
.update(req.body)
.digest('hex');

const valid =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return res.sendStatus(401);

const event = JSON.parse(req.body.toString('utf8'));
// Idempotent processing: event.transactionID, event.status
res.sendStatus(200);
},
);

Respond to the notification​

  • Reply with a 2xx status to acknowledge receipt.
  • Reply within 2 seconds: after that, MoyaPay gives up on the call. Acknowledge first, process afterwards (queue, background task).

Reliability: what to plan for​

Do not rely on callbacks alone

The notification is sent at the moment the final status is recorded. There is no automatic retry schedule if your server is down or too slow to answer at that moment.

  • If you have not received a callback for a transaction after a reasonable delay, query its status.
  • Make your processing idempotent: the same transaction may be notified more than once, and a FAILED can be followed by a SUCCESS (see Statuses).
  • Keep your signing secret server-side and never publish it (code repository, mobile app, etc.).