n8n-io/n8n · warning · BadRequestError
Invalid nps survey state structure
Error message
Invalid nps survey state structure
What it means
Thrown by PATCH /user-settings/nps-survey when req.body is not a structurally valid NPS survey state. The local getNpsSurveyState() validator only accepts two shapes: a 'responded' state ({responded:true, lastShownAt:number}) or a 'waitingForResponse' state ({waitingForResponse:true, ignoredCount:number, lastShownAt:number}); both require lastShownAt to be a number. Any other object, non-object, missing fields, or wrong types causes this BadRequestError (HTTP 400).
Source
Thrown at packages/cli/src/controllers/user-settings.controller.ts:46
return {
waitingForResponse: true,
ignoredCount: state.ignoredCount,
lastShownAt: state.lastShownAt,
};
}
return;
}
@RestController('/user-settings')
export class UserSettingsController {
constructor(private readonly userService: UserService) {}
@Patch('/nps-survey')
async updateNpsSurvey(req: NpsSurveyRequest.NpsSurveyUpdate): Promise<void> {
const state = getNpsSurveyState(req.body);
if (!state) {
throw new BadRequestError('Invalid nps survey state structure');
}
await this.userService.updateSettings(req.user.id, {
npsSurvey: state,
});
}
}
View on GitHub (pinned to 5ac6606e81)
Solutions
- Ensure the request body matches one of the two accepted shapes exactly: {responded:true,lastShownAt:<number>} OR {waitingForResponse:true,ignoredCount:<number>,lastShownAt:<number>}.
- Verify lastShownAt is a numeric Unix-style timestamp (ms), not an ISO string or Date.
- If adding a new state variant, extend getNpsSurveyState() in user-settings.controller.ts and the NpsSurveyState type in n8n-workflow before relying on it.
Example fix
// before
await patch('/user-settings/nps-survey', { responded: true });
// after
await patch('/user-settings/nps-survey', {
responded: true,
lastShownAt: Date.now(),
}); Defensive patterns
Strategy: validation
Validate before calling
function buildNpsState(input) {
if (typeof input.lastShownAt !== 'number' || !Number.isFinite(input.lastShownAt)) {
throw new Error('lastShownAt must be a finite number');
}
if (input.responded === true) return { responded: true, lastShownAt: input.lastShownAt };
if (input.waitingForResponse === true && typeof input.ignoredCount === 'number') {
return { waitingForResponse: true, ignoredCount: input.ignoredCount, lastShownAt: input.lastShownAt };
}
throw new Error('State must be responded:true or waitingForResponse:true+ignoredCount:number');
}
// call before PATCH /user-settings/nps-survey
const body = buildNpsSurveyState(rawState); Type guard
function isNpsSurveyState(s: unknown): s is { lastShownAt: number } & (
| { responded: true }
| { waitingForResponse: true; ignoredCount: number }
) {
if (typeof s !== 'object' || s === null) return false;
const o = s as Record<string, unknown>;
if (typeof o.lastShownAt !== 'number') return false;
if (o.responded === true) return true;
return o.waitingForResponse === true && typeof o.ignoredCount === 'number';
} Prevention
- Always set lastShownAt to Date.now() (a number), never an ISO string.
- Send exactly one of the two accepted state variants; never both.
- Mirror getNpsSurveyState() logic in the client to fail before the round-trip.
When it happens
Trigger: Calling PATCH /user-settings/nps-survey with a body that is not an object, lacks lastShownAt, has lastShownAt as a non-number, or declares neither responded:true nor the waitingForResponse+ignoredCount combo (e.g. {responded:true} alone, or {lastShownAt:'2024-01-01'}).
Common situations: Frontend NPS survey component sending a partial state object during an A/B test variant, a stale frontend shipping an outdated state schema after a backend bump, hand-crafted curl/automation with malformed JSON, or a serializer stripping numeric fields to strings.
Related errors
- error.message
- Missing binary data ID
- Malformed binary data ID
- Invalid binary data mode
- Token is required
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/9efb32ea23314fdd.
Report an issue: GitHub.