Errors
Understand Seev API error codes, HTTP status codes, and how to handle them gracefully.
The Seev API uses conventional HTTP status codes and returns a consistent JSON error envelope on every failure. This page explains the error format, lists all error codes, and covers best practices for handling errors gracefully in your integration.
Error response format
All API errors return a JSON body with the same shape:
{
"status": "error",
"code": "INVALID_API_KEY",
"message": "The provided API key is invalid or has been revoked.",
"param": null
}| Field | Type | Description |
|---|---|---|
status | "error" | Always "error" for failure responses. |
code | string | Machine-readable error code. Use this for programmatic handling. |
message | string | Human-readable description. Suitable for logs — not for end-user display. |
param | string | null | The specific request field that caused the error, if applicable. |
HTTP status codes
| Status | Meaning |
|---|---|
200 | Success |
400 | Bad request — invalid or missing parameters |
401 | Unauthenticated — missing or invalid API key |
403 | Forbidden — the key is valid but lacks permission for this action |
404 | Not found — the resource does not exist |
409 | Conflict — duplicate request (check Idempotency) |
422 | Unprocessable — request is well-formed but semantically invalid |
429 | Rate limited — too many requests |
500 | Internal server error — something went wrong on Seev's end |
Error codes
Authentication
| Code | Status | Description |
|---|---|---|
INVALID_API_KEY | 401 | The API key is missing, malformed, or revoked. |
KEY_MISMATCH | 401 | The public key and secret key belong to different accounts. |
FORBIDDEN | 403 | The key does not have permission for the requested operation. |
Request validation
| Code | Status | Description |
|---|---|---|
MISSING_PARAM | 400 | A required parameter was not provided. param contains the field name. |
INVALID_PARAM | 400 | A parameter was provided but has an invalid value or type. |
INVALID_AMOUNT | 400 | The amount value is zero, negative, or non-numeric. |
INVALID_CURRENCY | 400 | The currency code is not supported. |
INVALID_PAYLOAD | 400 | The request body could not be parsed (e.g. malformed JSON or webhook body). |
Payments
| Code | Status | Description |
|---|---|---|
CHARGE_FAILED | 422 | The payment was declined by the processor. |
CHARGE_EXPIRED | 422 | The payment session timed out before the customer completed the flow. |
DUPLICATE_CHARGE | 409 | A charge with the same idempotency key already exists. |
INVALID_SESSION | 404 | The checkout session ID is not found or has already been used. |
REFUND_EXCEEDS_AMOUNT | 422 | The refund amount is greater than the original charge or remaining refundable balance. |
ALREADY_REFUNDED | 409 | The charge has already been fully refunded. |
Webhooks
| Code | Status | Description |
|---|---|---|
INVALID_SIGNATURE | 401 | The webhook signature does not match — the payload may have been tampered with or the wrong secret is being used. |
MISSING_EVENT_TYPE | 400 | The webhook body does not contain an event field. |
Infrastructure
| Code | Status | Description |
|---|---|---|
RATE_LIMITED | 429 | You have exceeded the request rate limit. Back off and retry after the Retry-After header value. |
NOT_FOUND | 404 | The requested resource does not exist. |
INTERNAL_ERROR | 500 | An unexpected error occurred on the Seev API. Retry with exponential backoff. |
NETWORK_ERROR | — | (SDK only) The request never reached the API — check connectivity. |
Handling errors with the payment gateway
When using Seev.charge() to create a checkout session, errors generally fall into two categories:
1. Validation errors (before the customer sees anything)
These happen when your server calls Seev.charge() with invalid parameters. Catch them and respond to your user with a meaningful message — do not redirect to the checkout URL.
2. Payment errors (after the customer completes the flow)
These surface when you call Seev.callback() or Seev.transaction() to verify the result. A CHARGE_FAILED or CHARGE_EXPIRED code means the payment did not go through — show an error and let the customer try again.
Example
import Seev from '@seev/sdk';
// --- Creating a charge ---
let checkoutUrl: string;
try {
const charge = await Seev.charge({
type: 'checkout',
amount: 5000,
currency: 'GHS',
recipient: { name: 'Jane Doe', email: 'jane@example.com' },
});
checkoutUrl = charge.redirect_url;
} catch (error) {
if (error instanceof Error) {
// Log the raw message for debugging
console.error('[Seev] Charge failed:', error.message);
// Return a generic message to the client — never expose raw API errors
return res.status(500).json({ error: 'Could not initiate payment. Please try again.' });
}
}
// --- Verifying the result after redirect ---
try {
const result = await Seev.callback(req.body);
if (result.status === 'success') {
// Fulfil the order
} else {
// Payment declined — prompt the user to retry
return res.status(400).json({ error: 'Payment was not completed.' });
}
} catch (error) {
if (error instanceof Error) {
console.error('[Seev] Callback error:', error.message);
return res.status(400).json({ error: 'Invalid payment callback.' });
}
}Best practices
Never display raw API error messages to users
The message field is intended for logs and developers. It may contain internal details that are confusing or misleading to end users. Map error codes to friendly copy in your UI instead.
Use code for programmatic handling
Always branch on code, not message. The message may change across API versions; the code is stable.
switch (errorCode) {
case 'CHARGE_FAILED':
return 'Your payment was declined. Please try a different payment method.';
case 'CHARGE_EXPIRED':
return 'Your payment session timed out. Please start again.';
default:
return 'Something went wrong. Please try again or contact support.';
}Retry 5xx errors with exponential backoff
INTERNAL_ERROR (500) and RATE_LIMITED (429) are transient. Retry with increasing delays — for example, 1s, 2s, 4s — with a maximum of 3–4 attempts. Do not retry 4xx errors as they indicate a problem with the request itself.
Log the full error context
Include the error code, message, param, and the relevant resource ID (e.g. transaction reference) in your logs so you can correlate failures in your monitoring tools.
Handle webhook signature failures silently
If Seev.webhook() throws an INVALID_SIGNATURE error, return a 200 response anyway — do not return 401 or 400. Returning a non-2xx status will cause Seev to retry the delivery, which can amplify noise from malformed requests. Just discard the event and log the failure.