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

  1. Return exactly { "contents": [{ "msg": string, "order": number }, ...] } with a non-empty array.
  2. Ensure order is a JSON number and msg is present and a string on every entry.
  3. Iterate against POST /api/v1/livechat/triggers/external-service/test (rate-limited to 15 req/min) with your serviceUrl until it returns success.
  4. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/309a95cf62ec9d1a. Report an issue: GitHub.