Overview
Reliable integrations depend on handling failures well, and there are two directions to think about: your system calling the Mobaro API, and Mobaro sending webhooks to your endpoint. This article focuses on the first — the HTTP responses the Mobaro API returns and how to react to each — then points you to the webhook-receiver side at the end.
Most Mobaro API endpoints return a consistent set of status codes. Your integration should detect them and react appropriately: fix auth, correct the request, or retry safely. The common responses are 401 Unauthorized, 404 Not Found, 429 Too Many Requests, and 503 Service Unavailable (plus 400/500) across endpoints like Users, Timesheets, and Webhooks.
Quick reference: what each code means and what to do
Code | What it means | Action you should take |
401 Unauthorized | Missing or invalid | Verify the header, key validity, and environment. Do not retry until fixed. |
404 Not Found | Resource or route not found. | Check IDs, routes, and filters. Do not blind-retry. |
429 Too Many Requests | You hit a rate limit. | Back off exponentially and retry after a delay. |
503 Service Unavailable | Temporary service issue. | Retry with backoff and jitter; give up after a few attempts. |
Best practice: Many list endpoints support Limit and Offset. Use them to paginate and avoid hitting limits when fetching large datasets.
Handling each error in practice
401 Unauthorized
Ensure the header is exactly
X-Api-Key: YOUR_SECRET_TOKEN.Confirm the key hasn't been rotated or restricted.
Only retry after fixing credentials. Blind retries waste quota and time.
404 Not Found
Re-check the resource path and the ID you're requesting (for example
/users/{id},/timesheets/{id}).If looking up by filters, verify the record exists first (list → pick ID → get).
429 Too Many Requests (rate limits)
Back off and retry. Use exponential backoff (for example 1s, 2s, 4s, 8s) with jitter to avoid thundering herds.
Break large exports into pages using
Limit/Offset.Stagger scheduled jobs and avoid concurrent high-volume calls.
503 Service Unavailable
Treat as temporary. Retry with exponential backoff and jitter.
Stop after a capped number of attempts (for example 3–6) and alert your team.
Safe retry pattern (pseudocode)
maxAttempts = 5
delay = 1s
for attempt in 1..maxAttempts:
resp = call_api()
if resp.status in [200..299]: return resp
if resp.status == 401 or resp.status == 404:
abort (fix config/IDs; do not auto-retry)
if resp.status in [429, 503]:
sleep(delay + random_jitter())
delay *= 2
continue -> raise error with context
Minimal, copy-paste retry helpers
Python (requests)
import time, random, requests
API = "https://app.mobaro.com/api/customers/users"
HEADERS = {"X-Api-Key": "YOUR_SECRET_TOKEN"}
def get_with_retry(url, headers, attempts=5, base=1.0):
delay = base
for i in range(attempts):
r = requests.get(url, headers=headers)
if 200 <= r.status_code < 300:
return r
if r.status_code in (401, 404):
raise RuntimeError(f"Non-retryable {r.status_code}: {r.text}")
if r.status_code in (429, 503):
time.sleep(delay + random.uniform(0, 0.5))
delay *= 2
continue
r.raise_for_status()
raise TimeoutError("Max retries exceeded")
resp = get_with_retry(API, HEADERS)
print(resp.json())
Node.js (fetch)
import fetch from "node-fetch";
async function getWithRetry(url, opts = {}, attempts = 5, base = 1000) {
let delay = base;
for (let i = 0; i < attempts; i++) {
const res = await fetch(url, opts);
if (res.ok) return res;
if (res.status === 401 || res.status === 404) {
throw new Error(`Non-retryable ${res.status}`);
}
if (res.status === 429 || res.status === 503) {
await new Promise(r => setTimeout(r, delay + Math.random() * 250));
delay *= 2;
continue;
}
throw new Error(`${res.status} ${await res.text()}`);
}
throw new Error("Max retries exceeded");
}
const res = await getWithRetry(
"https://app.mobaro.com/api/customers/users",
{ headers: { "X-Api-Key": "YOUR_SECRET_TOKEN" } }
);
console.log(await res.json());
Observability checklist
Log the endpoint, status code, attempt number, and delay used.
Alert on repeated 401s (key/config problem) and spikes of 429/503 (load or scheduling issue).
Record the last successful page or ID so your job can resume after a failure.
Make creates and updates idempotent (for example upsert by external ID) so retries are safe.
The other direction: receiving webhooks
Everything above is about your system calling the Mobaro API. When Mobaro sends webhooks to your endpoint, the reliability model flips — you're the server, and the roles reverse:
Return a 2xx quickly. Acknowledge the delivery fast (under ~5s) and do heavier processing asynchronously. A slow or error response is treated as a failed delivery.
Be idempotent. A delivery can arrive more than once (including on a manual retry), so key your processing on the payload's
objectIdandtimestamp.Let Mobaro's delivery log do the tracking. Failed deliveries are recorded and can be re-sent — you don't need to build your own catch-up polling.
Note: For webhook setup and the payload shape, see Using webhooks in Mobaro. For the delivery log and retrying a failed delivery, see Monitoring webhook deliveries and retrying failed events.
Frequently asked questions
Q: Why am I getting 429s during large exports?
A: Likely too many requests in a short window. Paginate with Limit / Offset and add backoff between pages.
Q: Should I ever retry 401 or 404?
A: No. Fix credentials (401) or identifiers and paths (404) first, then try again.
Q: Are these codes consistent across endpoints?
A: Yes. Endpoints like Users, Timesheets, and Webhooks share the same 400/401/404/429/500/503 patterns.
Q: Does this retry logic apply to receiving webhooks too?
A: No — that's the reverse direction. When receiving webhooks, return a 2xx quickly and process idempotently; Mobaro's delivery log handles re-sending failures. See the webhook articles linked above.
