Skip to content

Errors

All domain errors extend the abstract MuraiError base class, which provides a typed code property for programmatic error handling.

Error hierarchy

MuraiError (abstract)
├── InsufficientBalanceError code: 'INSUFFICIENT_BALANCE'
├── IdempotencyConflictError code: 'IDEMPOTENCY_CONFLICT'
├── WebhookVerificationError code: 'WEBHOOK_VERIFICATION_FAILED'
├── InvalidAmountError code: 'INVALID_AMOUNT'
├── InvalidExpirationError code: 'INVALID_EXPIRATION'
├── InvalidMetadataError code: 'INVALID_METADATA'
└── GatewayError code: 'GATEWAY_ERROR'

InsufficientBalanceError

Thrown when a spend/debit exceeds the user’s available balance.

PropertyTypeDescription
code'INSUFFICIENT_BALANCE'Error code
userIdstringThe user whose balance is insufficient
requestednumberAmount requested
availablenumberBalance at time of check
import { InsufficientBalanceError } from '@murai-wallet/murai';
try {
await wallet.spend('user_123', 50_000, 'key-1');
} catch (error) {
if (error instanceof InsufficientBalanceError) {
console.log(error.userId); // 'user_123'
console.log(error.requested); // 50000
console.log(error.available); // 10000
console.log(error.code); // 'INSUFFICIENT_BALANCE'
}
}

IdempotencyConflictError

Thrown when a duplicate idempotency key is used for a ledger operation.

PropertyTypeDescription
code'IDEMPOTENCY_CONFLICT'Error code
idempotencyKeystringThe duplicate key
import { IdempotencyConflictError } from '@murai-wallet/murai';
try {
await wallet.spend('user_123', 1000, 'same-key');
await wallet.spend('user_123', 1000, 'same-key'); // throws
} catch (error) {
if (error instanceof IdempotencyConflictError) {
console.log(error.idempotencyKey); // 'same-key'
}
}

WebhookVerificationError

Thrown when a webhook’s signature verification fails.

PropertyTypeDescription
code'WEBHOOK_VERIFICATION_FAILED'Error code
import { WebhookVerificationError } from '@murai-wallet/murai';
try {
await checkout.handleWebhook({ payload, signature });
} catch (error) {
if (error instanceof WebhookVerificationError) {
return new Response('Unauthorized', { status: 401 });
}
}

InvalidAmountError

Thrown when an amount is not a positive integer.

PropertyTypeDescription
code'INVALID_AMOUNT'Error code
amountnumberThe invalid amount
await wallet.spend('user_123', -100, 'key'); // throws InvalidAmountError
await wallet.spend('user_123', 0, 'key'); // throws InvalidAmountError
await wallet.spend('user_123', 1.5, 'key'); // throws InvalidAmountError

InvalidExpirationError

Thrown when an expiresAt date is not in the future.

PropertyTypeDescription
code'INVALID_EXPIRATION'Error code
expiresAtDateThe invalid expiration date
import { InvalidExpirationError } from '@murai-wallet/murai';
try {
await wallet.topUp('user_123', 10_000, 'key-1', {
expiresAt: new Date('2020-01-01'), // in the past
});
} catch (error) {
if (error instanceof InvalidExpirationError) {
console.log(error.code); // 'INVALID_EXPIRATION'
console.log(error.expiresAt); // 2020-01-01T00:00:00.000Z
}
}

InvalidMetadataError

Thrown when the metadata string exceeds the allowed length or is otherwise invalid.

PropertyTypeDescription
code'INVALID_METADATA'Error code
metadatastringThe invalid metadata value
import { InvalidMetadataError } from '@murai-wallet/murai';
try {
await wallet.spend('user_123', 1000, 'key-1', {
metadata: 'x'.repeat(10_001), // exceeds max length
});
} catch (error) {
if (error instanceof InvalidMetadataError) {
console.log(error.code); // 'INVALID_METADATA'
}
}

GatewayError

Thrown when a payment gateway API call fails.

PropertyTypeDescription
code'GATEWAY_ERROR'Error code
gatewayNamestring'midtrans', 'xendit', or 'stripe'
httpStatusnumber | undefinedHTTP status code from the gateway
gatewayMessagestringError message from the gateway
causeunknownOriginal error (if any)
import { GatewayError } from '@murai-wallet/murai';
try {
await checkout.createSession({ /* ... */ });
} catch (error) {
if (error instanceof GatewayError) {
console.log(error.gatewayName); // 'midtrans'
console.log(error.httpStatus); // 401
console.log(error.gatewayMessage); // 'Invalid server key'
}
}

Catching any Murai error

Use the base class to catch all domain errors:

import { MuraiError } from '@murai-wallet/murai';
try {
await wallet.spend(userId, amount, key);
} catch (error) {
if (error instanceof MuraiError) {
// error.code is typed — use it for programmatic handling
switch (error.code) {
case 'INSUFFICIENT_BALANCE':
return { error: 'Not enough tokens' };
case 'IDEMPOTENCY_CONFLICT':
return { error: 'Already processed' };
default:
return { error: 'Something went wrong' };
}
}
throw error; // Re-throw unexpected errors
}