Skip to content

Guides

Production integration checklist

The operational practices that make a Mahvash integration dependable: secrets, timeouts, retries, observability, background work, webhooks, SDK configuration and data handling — with a checklist to review before launch.

Before launch

Review this list before real users depend on your integration:

  • The project API key lives only on your server, in a secret manager, with the smallest scopes it needs.
  • Staging and production use different projects and different keys.
  • Model choice comes from GET /v1/models or auto, and your code handles provider_unavailable.
  • Every call has a connect timeout and an overall timeout.
  • Output length (max_output_tokens), file sizes and user input length are bounded.
  • Retries reuse the same Idempotency-Key, and ambiguous outcomes are checked, not resent.
  • Logs record X-Request-ID, status and latency — never prompts, files, keys or tokens.
  • You have exercised a success, a validation error, a rate limit and a stream disconnect against a non-production project.
  • Project limits and a monthly budget are set, so a bug cannot run up unlimited cost.

Timeouts and retries

Set explicit timeouts. A reasoning model or a long document can take much longer than a normal HTTP call, so choose limits per operation instead of one global value, and prefer background mode for work that may take minutes.

const response = await fetch("https://api.mahvashai.ir/v1/responses", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.MAHVASH_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": job.idempotencyKey,
    "X-Request-ID": job.traceId,
  },
  body: JSON.stringify({ model: job.model, input: job.input, max_output_tokens: 800 }),
  signal: AbortSignal.timeout(90_000),
});
logger.info("mahvash call", {
  status: response.status,
  requestId: response.headers.get("X-Request-ID"), // log this, never the prompt or key
});
  • Honor 429 and Retry-After. Back off exponentially with jitter for 429 and 5xx.
  • A timeout after the request was sent is ambiguous. Retrieve the response or replay with the same idempotency key before sending new paid work.
  • Cap concurrency on your side too. Mahvash refuses runs beyond max_active_runs; queueing in your worker is cheaper than refused requests.

Observability

  • Send your own trace ID as X-Request-ID, or record the one Mahvash returns. Put it in every log line about the call and include it in support requests.
  • Store the response id, model and usage next to your own business record. This makes cost attribution and later investigation straightforward.
  • Track error rates by error.type and error.code, latency percentiles, and the share of incomplete responses.
  • Watch organization and project usage so a budget alert is not your first signal of a problem.

Background work and webhooks

For background responses you can poll GET /v1/responses/{id}, or let Mahvash call you. Webhook subscriptions are created per project by an administrator with a user JWT:

curl -X POST https://api.mahvashai.ir/api/v1/chat/projects/<project-uuid>/webhooks \
  -H "Authorization: Bearer $MAHVASH_USER_JWT" \
  -H "X-Mahvash-Organization: acme" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://hooks.example.com/mahvash", "event_types": ["response.completed", "response.failed"]}'

# The response contains the signing secret (whsec_...) exactly once.

Events cover response outcomes: response.completed, response.failed, response.cancelled and response.incomplete. The payload contains identifiers, status, model, usage and an error code — never prompts or output — so fetch the full response with its ID when you need content.

Verifying deliveries

Each delivery carries webhook-id, webhook-timestamp and webhook-signature headers in the Standard Webhooks format used by OpenAI. Verify the signature over the exact raw body with your whsec_... secret before trusting anything in it:

import crypto from "node:crypto";
import express from "express";

const app = express();
const secret = Buffer.from(process.env.MAHVASH_WEBHOOK_SECRET.replace(/^whsec_/, ""), "base64");

// Verify the exact raw bytes; a re-serialized JSON body will not match.
app.post("/mahvash", express.raw({ type: "application/json" }), async (req, res) => {
  const id = req.header("webhook-id");
  const timestamp = req.header("webhook-timestamp");
  const signatures = (req.header("webhook-signature") ?? "").split(" ");

  if (!id || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(400);

  const expected = "v1," + crypto
    .createHmac("sha256", secret)
    .update(id + "." + timestamp + ".")
    .update(req.body)
    .digest("base64");
  const valid = signatures.some((candidate) =>
    candidate.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(candidate), Buffer.from(expected)));
  if (!valid) return res.sendStatus(400);

  const event = JSON.parse(req.body.toString("utf8"));
  if (await db.webhookEvents.exists(event.id)) return res.sendStatus(200); // duplicate
  await db.webhookEvents.record(event.id);
  await jobs.enqueue("sync-response", { responseId: event.data.id }); // work outside the request
  res.sendStatus(200);
});
  • Reject deliveries whose timestamp is more than a few minutes old.
  • Deduplicate on the event id; the same event can arrive more than once.
  • Tolerate out-of-order delivery, and make the handler idempotent.
  • Respond quickly with 2xx and do the real work in a queue. A receiver that keeps failing is disabled automatically.
  • Rotate the secret with POST /api/v1/chat/webhooks/{id}/rotate; the old secret keeps signing for 24 hours so you can redeploy safely.

Using the OpenAI SDKs

For supported /v1 operations, the official OpenAI SDKs work with Mahvash when you set baseURL (JavaScript) or base_url (Python) to https://api.mahvashai.ir/v1 and pass a Mahvash project key. Keep these points in mind:

  • Mahvash-only request fields, such as asset_ids or managed_tools, are sent through the SDK's extra-body option or with direct HTTP.
  • Workspace endpoints under /api/v1 are not part of the OpenAI SDKs. Call them with plain HTTP and read the product envelope.
  • SDKs retry some failures automatically. Configure retries deliberately so they respect your idempotency strategy.
  • Pin your SDK version and test the quickstart, a stream, and an error path whenever you upgrade.

Data handling

  • Send only the data the task needs. Remove secrets, tokens and unnecessary personal data from prompts and files.
  • Use store: false for requests whose content should not be kept after delivery; retrieval and idempotent replay are then unavailable for that response.
  • Delete responses, files and vector stores you no longer need, according to your retention policy.
  • Keep your own authorization in front of everything you show users: Mahvash scopes data to projects, but only your application knows which of your users may see which document or answer.