RocketChat/Rocket.Chat · error · Error
External service response does not match expected format
Error message
External service response does not match expected format
What it means
Inside callTriggerExternalService, a successful HTTP response is parsed as JSON and must carry a top-level contents array that is non-empty and whose every element has msg:string and order:number. Any other shape throws this error; the caller maps it to the API error error-invalid-external-service-response (a fetch abort maps to error-timeout instead). Both livechat/triggers/external-service/test and livechat/triggers/:_id/external-service/call surface it.
Source
Thrown at apps/meteor/ee/server/api/v1/omnichannel/lib/triggers.ts:40
// SECURITY: Integrations can only be configured by users with enough privileges. It's ok to disable this check here.
ignoreSsrfValidation: true,
});
if (!response.ok || response.status !== 200) {
const text = await response.text();
throw new Error(text);
}
const data = await response.json();
const { contents } = data;
if (
!Array.isArray(contents) ||
!contents.length ||
!contents.every(({ msg, order }) => typeof msg === 'string' && typeof order === 'number')
) {
throw new Error('External service response does not match expected format');
}
return {
response: {
statusCode: response.status,
contents: data?.contents || [],
},
};
} catch (error: any) {
const isTimeout = error.message === 'The user aborted a request.';
return {
error: isTimeout ? 'error-timeout' : 'error-invalid-external-service-response',
response: error.message,
fallbackMessage,
};
}
}
View on GitHub (pinned to b2c16d5842)
Solutions
- Return exactly { "contents": [{ "msg": string, "order": number }, ...] } with a non-empty array.
- Ensure order is a JSON number and msg is present and a string on every entry.
- Iterate against POST /api/v1/livechat/triggers/external-service/test (rate-limited to 15 req/min) with your serviceUrl until it returns success.
- Add a contract test on the service side asserting this exact shape.
Example fix
// before (external service handler)
res.status(200).json({ messages: [{ msg: 'Hello', order: '1' }] });
// after
res.status(200).json({ contents: [{ msg: 'Hello', order: 1 }] }); Defensive patterns
Strategy: type-guard
Validate before calling
const res = await fetch(serviceUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-RocketChat-Livechat-Token': token },
body: JSON.stringify({ metadata: null, visitorToken: '1234567890' }),
});
const data = await res.json();
if (!isExternalServicePayload(data)) {
throw new Error('Service response must be { contents: [{ msg, order }] }');
} Type guard
const isExternalServicePayload = (d: unknown): d is { contents: { msg: string; order: number }[] } =>
typeof d === 'object' &&
d !== null &&
Array.isArray((d as { contents?: unknown }).contents) &&
(d as { contents: unknown[] }).contents.length > 0 &&
(d as { contents: unknown[] }).contents.every(
(c) => typeof (c as { msg?: unknown })?.msg === 'string' && typeof (c as { order?: unknown })?.order === 'number',
); Try / catch
try {
const r = await api.post('/v1/livechat/triggers/external-service/test', { webhookUrl });
} catch (e) {
if (e?.response?.data?.error === 'error-invalid-external-service-response') {
// fix the service to return { contents: [{ msg, order }] }, then re-test
} else if (e?.response?.data?.error === 'error-timeout') {
// service too slow: raise timeout or optimize the service
} else throw e;
} Prevention
- Contract-test the external service against the exact contents/msg/order shape, including numeric order.
- Never wrap the payload in a custom envelope.
- Use the rate-limited /test endpoint to iterate before wiring the trigger.
When it happens
Trigger: Your external service returns HTTP 200 with {}, {"contents": []}, {"messages":[...]}, or entries like {msg: 'Hi', order: '1'} (order as string) or entries missing msg. The JSON parse itself must also succeed, so the body must be actual JSON.
Common situations: Webhook implemented from a different spec or with its own envelope; field named message instead of msg; order serialized as a string; services that return an empty contents array when no agent reply is available.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Livechat secret token is not configured
- Invalid trigger
- Trigger is not configured to use an external service
- Invalid service URL
- error-invalid-file-uploaded
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/309a95cf62ec9d1a.
Report an issue: GitHub.