PerxDevelopers

Core concepts

Errors

Perx separates transport failures from business outcomes. Knowing which is which is what stops your integration retrying things that will never succeed.

Error shape

{
  "statusCode": 422,
  "message": "Payload validation failed",
  "code": "validation_failed",
  "fields": [
    { "path": "transaction.items[0].unitPrice", "message": "must be a positive number with at most 2 decimal places" }
  ],
  "requestId": "5f2c9a1e-0d47-4a2b-9c88-31ea7b40d6f1"
}

Machine-readable codes

codeWhat went wrongFix
bad_signatureThe HMAC did not match.Almost always a re-serialised body. See Authentication.
stale_timestampTimestamp outside the 300-second window.Check NTP. On queued retries, re-sign with a fresh timestamp.
unknown_connectionNo active connection for that vendor + store.The merchant has not connected, used a different store ID, or disconnected.
store_mismatchstore.id differs from the X-Perx-Store header.Usually a bug where one is hardcoded.
validation_failedThe payload does not match the schema.Read fields[] — it names each path.
Unknown fields are rejected, not ignored

Perx validates strictly, so a typo like unit_price instead of unitPrice fails loudly during certification rather than silently zeroing a price in production. If you are getting 422 on a field you believe is correct, check the exact spelling and casing against the reference.

Business outcomes that are not errors

These all return 200. Treating any of them as a failure will cause pointless retries and false alerts.

OutcomeWhy it is a success
Unknown customer (warning set)The sale was recorded. The customer simply is not a Perx member yet — Perx invites them by SMS.
status: "duplicate"Idempotency worked. Your retry was absorbed and nothing was double-counted.
status: "ignored"An event type Perx does not act on. Accepted so you stop resending it.
NOTHING_TO_REVERSE on a refundThe original sale never reached Perx or earned nothing. There is nothing to undo.

Showing errors to cashiers

On the redemption endpoints, the message field on a 400 is written for the person at the till — “Minimum spend of MVR 300 not met”, not a stack trace. Display it verbatim. Replacing it with a generic “Something went wrong” is the difference between a cashier resolving the situation and a cashier calling support.

Getting help

Every response carries X-Request-ID, echoed as requestId in error bodies. Perx logs every request against it — quote it and support can trace the exact delivery in seconds. Without it, a report of “webhooks are failing” takes far longer to diagnose.