Handling failed submissions and offline users
Keep the visitor's text on screen whenever a send fails. Retry network errors and 429 responses with a short backoff, and fix the request for 400, 403, 404 and 413, since retrying those will fail again. Test each path with the network turned off and with a bad key.
By Rafael Thayto 路 Published 路 Last updated
What do the feedback API error codes mean?
The REST API returns a status code for every failed request, and each one points to a different fix. A 400 means the body is invalid, most often because the key or the message is missing or shorter than two characters. A 403 means the origin of the request is not on the product's allowed list. A 404 means the key does not match any product. A 413 means the body is larger than 16 KB. A 429 means the same product received more than 10 requests per minute from the same IP address.
A successful submission returns 201 with a JSON body that holds ok: true and the new feedback id. Treat anything else as a failure and show the visitor a message that matches the cause.
Which failures should you retry and which should you fix?
Retrying a request that will fail again only wastes the visitor's time and adds load to the server. Sort the codes into two groups:
| Code | Meaning | Action | Retry? |
|---|---|---|---|
| 400 | The body is invalid or a required field is missing | Check the key, the message length and the field types | No, fix the request |
| 403 | The site origin is not allowed | Add the site to the product's allowed origins | No, fix the settings |
| 404 | The public key is unknown | Check the key, and update it after a key rotation | No, fix the key |
| 413 | The body is over 16 KB | Shorten the message or drop large metadata | No, shorten it |
| 429 | Too many requests from one IP for this product | Wait, then send again with a delay | Yes, with backoff |
| Network error | The request never reached the server | Keep the text and offer to try again | Yes, when the connection returns |
The rate limit is per IP and per product, so a shared office network can hit it faster than a single person would. A short delay usually clears it.
How do you keep the message when a send fails?
The worst outcome is a failed send that clears the text. Keep the message in state until the server confirms success, and only clear it after a 201 response. If the send fails, leave the text in place, show a clear message next to the send button and move focus to that message so a screen reader user hears it.
async function submitFeedback(payload) {
const response = await fetch("https://escutaproduto.com/api/v1/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (response.status === 201) return { ok: true };
return { ok: false, status: response.status };
}
The function returns the status instead of throwing, so the form can choose the message for each code. A network failure makes fetch reject, so wrap the call in try and catch and treat a rejection as a network error.
How do you back off after a 429 response?
Retry a 429 a few times with growing delays, then stop and tell the visitor to try again later. Limit the retries, so a broken form does not loop forever:
async function sendWithBackoff(payload, attempt = 0) {
let result;
try {
result = await submitFeedback(payload);
} catch (error) {
result = { ok: false, status: 0 };
}
if (result.ok) return result;
const retryable = result.status === 0 || result.status === 429;
if (!retryable || attempt >= 3) return result;
await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** attempt));
return sendWithBackoff(payload, attempt + 1);
}
The delays are one, two and four seconds. Show the visitor a status line while the retries run, so the form does not look frozen.
How do you handle a visitor who is offline?
Offline visitors are a different case. Do not guess the connection state from navigator.onLine alone, because the browser reports a connection even when the server cannot be reached. Instead, rely on the failed request itself, and use the online event to retry a message that is waiting.
For your own form, you can store an unsent message in localStorage and send it when the connection returns. Wrap every read and write in try and catch, because storage can be full or blocked in private windows. Remove the stored draft only after the server returns 201. Keep the saved text private to the visitor's browser, and never store anything you would not want on a shared computer.
What does the widget do on failure, and how do you test it?
Do not assume the widget behaves like your own form. Test it. Open the widget, type a message, turn off the network in DevTools or switch off Wi-Fi, then send. Check whether the text is still in the panel when the network returns, and whether the visitor can send it again without retyping. Then repeat the test with a page that is not in the allowed origins list, which produces a 403. Write down what you see, and adjust the instructions on your site. If the text is lost on a network failure, tell visitors to copy a long message before they send.
Run these checks before you release a form change:
- Send a valid message and confirm the
201path clears the form. - Turn off the network, send, and confirm the text stays.
- Send from an origin that is not allowed and confirm the
403message makes sense to a visitor. - Send a message of 17 KB and confirm the
413message appears. - Send several messages in a row and confirm the
429path shows a wait message.
Recovering failed sends in an Escuta Produto integration
Use the widget for the common path and your own form only when you need custom error text or a different layout. The request format, the allowed origins and the key rules are in the API docs. The steps for adding the widget to a page are in the widget docs. For the other side of a failed send, when a message does arrive, how to triage customer feedback explains how to sort it. For spam protection that can cause a request to be rejected, how to protect a feedback form from spam covers the origin and rate rules in more depth.
Frequently asked questions
Should a feedback form retry every failed submission?
No. Retry network errors and 429 responses, with a short delay and a limit on attempts. Fix the request for 400, 403, 404 and 413, because sending the same body again will fail the same way.
What should happen to the visitor's message when a send fails?
The text should stay in the form. Clear it only after the server confirms success with a 201 response, and show a message that says what went wrong and what to do next.
What does a 429 error mean for a feedback form?
The same IP address sent too many requests to the same product in a short period, which is more than 10 per minute. Wait a few seconds, then send again, and do not loop retries without a limit.
Related
- Feedback widget best practicesWhere to place a feedback button, how to keep it fast and accessible, and how to protect it from spam. Practical rules for in-app feedback widgets.
- What should a feedback button say?How to word a feedback button: verbs or nouns, labels that match each page and feedback type, localized copy, and how to replace the default Feedback button.
- Designing a feedback widget for mobile screensDesigning a feedback widget for phones: thumb reach, keyboard overlap, bottom navigation conflicts and the checks to run on a real device before you ship.
- How to make a feedback widget accessibleMake a feedback widget usable with a keyboard and screen reader: real buttons, dialog semantics, focus return, labels, contrast and known gaps.