juspay/hyperswitch · error
Invalid HEX color: "${hex}"
Error message
Invalid HEX color: "${hex}" What it means
Thrown by hexToRgbArray(), a vendored copy of onury/invert-color embedded in the hosted payment-link page (crates/router/src/core/payment_link/payment_link_initiate/payment_link.js). After stripping an optional leading '#', the string must match /^(?:[0-9a-f]{3}){1,2}$/i, i.e. exactly 3 or 6 hex digits. The page calls invert(primaryColor, true) and invert(chosenColor, true) in initializeEventListeners (lines 330 and 365) to pick a contrasting button text colour, so any merchant branding value that is not a 3/6-digit hex string crashes colour setup for the checkout page.
Source
Thrown at crates/router/src/core/payment_link/payment_link_initiate/payment_link.js:116
var hex = Math.round(c).toString(16);
return hex.length === 1 ? "0" + hex : hex;
};
return "#" + toHex(r) + toHex(g) + toHex(b);
}
/**
* Ref - https://github.com/onury/invert-color/blob/master/lib/cjs/invert.js
*/
function padz(str, len) {
if (len === void 0) {
len = 2;
}
return (new Array(len).join("0") + str).slice(-len);
}
function hexToRgbArray(hex) {
if (hex.slice(0, 1) === "#") hex = hex.slice(1);
var RE_HEX = /^(?:[0-9a-f]{3}){1,2}$/i;
if (!RE_HEX.test(hex)) throw new Error('Invalid HEX color: "' + hex + '"');
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
return [
parseInt(hex.slice(0, 2), 16),
parseInt(hex.slice(2, 4), 16),
parseInt(hex.slice(4, 6), 16),
];
}
function toRgbArray(c) {
if (!c) throw new Error("Invalid color value");
if (Array.isArray(c)) return c;
return typeof c === "string" ? hexToRgbArray(c) : [c.r, c.g, c.b];
}
function getLuminance(c) {
var i, x;
var a = [];
for (i = 0; i < c.length; i++) {View on GitHub (pinned to 9b8b89dc37)
Solutions
- Set the payment link's theme / payment_button_colour to a strict 3- or 6-digit hex string with or without '#', e.g. '#1A2B3C'
- Validate the colour at the API boundary where payment link / merchant branding is created, rejecting non-hex values there
- In the page JS, default the colour before calling invert(): var primaryColor = paymentDetails.theme || '#000000' and likewise for payment_button_colour
- If untrusted values must still render, wrap the invert()/adjustLightness() calls in try/catch and fall back to '#ffffff'/'#000000' so the page still loads
- Only widen the regex if you also update adjustLightness(), which makes the same hex assumptions
Example fix
// before (payment_link.js line 327-330)
var primaryColor = paymentDetails.theme;
var contrastBWColor = invert(primaryColor, true);
// after
var HEX_RE = /^#?(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
var primaryColor =
typeof paymentDetails.theme === 'string' && HEX_RE.test(paymentDetails.theme.trim())
? paymentDetails.theme
: '#1a2b3c';
var contrastBWColor = invert(primaryColor, true); Defensive patterns
Strategy: validation
Validate before calling
// Run before using paymentDetails.theme / payment_button_colour
const HEX_RE = /^#?(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
function assertValidHexColor(color, field) {
if (typeof color !== 'string' || !HEX_RE.test(color.trim())) {
throw new Error(`${field} must be a 3- or 6-digit hex color, got: ${JSON.stringify(color)}`);
}
}
assertValidHexColor(req.body.theme, 'theme'); Type guard
/** @param {unknown} c @returns {c is string} */
function isHexColor(c) {
return typeof c === 'string' && /^#?(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(c.trim());
} Try / catch
// Last-resort guard around the vendored invert()
try {
contrastBWColor = invert(primaryColor, true);
} catch (e) {
if (/Invalid HEX color/.test(e.message)) {
contrastBWColor = '#ffffff'; // sane default, page still renders
} else {
throw e;
}
} Prevention
- Validate branding colours as strict hex at the API boundary where payment links / merchant profiles are created
- Never accept CSS named colours, rgb()/hsl(), or 8-digit alpha hex in fields destined for invert()
- Keep the vendored invert-color copies in payment_link.js and status.js in sync when patching
- Add a unit test asserting the page renders with theme unset and with '#ABC' shorthand
When it happens
Trigger: A payment link initiated with paymentDetails.theme or payment_button_colour set to a CSS keyword ('white'), an rgb()/hsl() string, a 4- or 8-digit hex ('#RGBA', '#RRGGBBAA'), a 5-digit value ('#12345'), a value with surrounding whitespace, or garbage like '#GGGGGG'. The value reaches invert() at line 330 (theme) or 365 (payment_button_colour || theme).
Common situations: Merchant branding saved through an API path that never validated the colour format; a client sending 'rgba(0,0,0,1)' or 8-digit alpha hex that CSS accepts but invert-color rejects; DB fields where theme is null being replaced by the literal string 'null'; version drift if branding used to accept named colours.
Related errors
- Invalid HEX color: "${hex}"
- Invalid color value
- Invalid color value
- Please provide a baseUrl
- Unsupported baseUrl
AI-assisted analysis of juspay/hyperswitch@9b8b89dc37 (2026-08-16).
Data as JSON: /api/errors/0024841e939ce3ed.
Report an issue: GitHub.