feat: add warning with missing signature secret

This commit is contained in:
Sabiha Khan 2026-06-29 15:06:00 +05:30
parent f644933e28
commit e1e9782c48
10 changed files with 122 additions and 20 deletions

View file

@ -53,6 +53,7 @@ export default function TelephonyConfigurationsPage() {
const { user, getAccessToken, loading: authLoading } = useAuth();
const {
telnyxMissingWebhookPublicKeyCount,
vonageMissingSignatureSecretCount,
refresh: refreshWarnings,
} = useTelephonyConfigWarnings();
const [items, setItems] = useState<TelephonyConfigurationListItem[]>([]);
@ -82,9 +83,9 @@ export default function TelephonyConfigurationsPage() {
}
}, [authLoading, user, getAccessToken]);
// After a save (create/update), the backing config may have flipped between
// missing/present webhook_public_key — refresh the cached warning state so
// the page banner and nav badge update without a manual reload.
// After a save (create/update), webhook-verification warning state may have
// changed — refresh the cached warning state so the page banner and nav badge
// update without a manual reload.
const onSaved = useCallback(async () => {
await fetchItems();
await refreshWarnings();
@ -194,6 +195,26 @@ export default function TelephonyConfigurationsPage() {
</div>
)}
{vonageMissingSignatureSecretCount > 0 && (
<div className="mb-6 rounded-md border border-amber-300 bg-amber-50 p-4 text-amber-900 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-200">
<div className="flex items-start gap-3">
<AlertTriangle className="h-5 w-5 shrink-0 mt-0.5" />
<div className="space-y-1 text-sm">
<p className="font-medium">Signature secret not configured</p>
<p>
{vonageMissingSignatureSecretCount === 1
? "1 Vonage configuration is"
: `${vonageMissingSignatureSecretCount} Vonage configurations are`}{" "}
missing a signature secret. Without it, Vonage signed webhooks
are rejected, so inbound calls and call status updates will not
work. Copy the signature secret from your Vonage account and
paste it into the affected Vonage configuration below.
</p>
</div>
</div>
</div>
)}
{loading ? (
<div className="grid gap-3">
<Skeleton className="h-24 w-full" />

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -5323,6 +5323,10 @@ export type TelephonyConfigWarningsResponse = {
* Telnyx Missing Webhook Public Key Count
*/
telnyx_missing_webhook_public_key_count: number;
/**
* Vonage Missing Signature Secret Count
*/
vonage_missing_signature_secret_count: number;
};
/**
@ -6427,6 +6431,12 @@ export type VonageConfigurationRequest = {
* Private key for JWT generation
*/
private_key: string;
/**
* Signature Secret
*
* Vonage signature secret used to verify signed webhooks
*/
signature_secret?: string | null;
/**
* From Numbers
*
@ -6461,6 +6471,10 @@ export type VonageConfigurationResponse = {
* Private Key
*/
private_key: string;
/**
* Signature Secret
*/
signature_secret?: string | null;
/**
* From Numbers
*/
@ -7553,6 +7567,27 @@ export type HandleVonageEventsApiV1TelephonyVonageEventsWorkflowRunIdPostRespons
200: unknown;
};
export type HandleVonageEventsWithoutRunApiV1TelephonyVonageEventsPostData = {
body?: never;
path?: never;
query?: never;
url: '/api/v1/telephony/vonage/events';
};
export type HandleVonageEventsWithoutRunApiV1TelephonyVonageEventsPostErrors = {
/**
* Not found
*/
404: unknown;
};
export type HandleVonageEventsWithoutRunApiV1TelephonyVonageEventsPostResponses = {
/**
* Successful Response
*/
200: unknown;
};
export type ImpersonateApiV1SuperuserImpersonatePostData = {
body: ImpersonateRequest;
headers?: {

View file

@ -167,8 +167,13 @@ export function AppSidebar() {
const { provider, getSelectedTeam, logout, user } = useAuth();
const { config } = useAppConfig();
const { openHireExpert } = useLeadForms();
const { telnyxMissingWebhookPublicKeyCount } = useTelephonyConfigWarnings();
const hasTelephonyWarning = telnyxMissingWebhookPublicKeyCount > 0;
const {
telnyxMissingWebhookPublicKeyCount,
vonageMissingSignatureSecretCount,
} = useTelephonyConfigWarnings();
const hasTelephonyWarning =
telnyxMissingWebhookPublicKeyCount > 0 ||
vonageMissingSignatureSecretCount > 0;
const isCollapsed = !isMobile && state === "collapsed";
// Get selected team for Stack auth (cast to Team type from Stack)

View file

@ -7,12 +7,14 @@ import { useAuth } from '@/lib/auth';
interface TelephonyConfigWarningsContextType {
telnyxMissingWebhookPublicKeyCount: number;
vonageMissingSignatureSecretCount: number;
refresh: () => Promise<void>;
loading: boolean;
}
const TelephonyConfigWarningsContext = createContext<TelephonyConfigWarningsContextType>({
telnyxMissingWebhookPublicKeyCount: 0,
vonageMissingSignatureSecretCount: 0,
refresh: async () => { },
loading: false,
});
@ -23,7 +25,8 @@ const TelephonyConfigWarningsContext = createContext<TelephonyConfigWarningsCont
// change. Page-level callers invalidate via refresh() after a save.
export function TelephonyConfigWarningsProvider({ children }: { children: ReactNode }) {
const auth = useAuth();
const [count, setCount] = useState(0);
const [telnyxCount, setTelnyxCount] = useState(0);
const [vonageCount, setVonageCount] = useState(0);
const [loading, setLoading] = useState(false);
const hasFetched = useRef(false);
@ -31,9 +34,11 @@ export function TelephonyConfigWarningsProvider({ children }: { children: ReactN
setLoading(true);
try {
const res = await getTelephonyConfigWarningsApiV1OrganizationsTelephonyConfigWarningsGet();
setCount(res.data?.telnyx_missing_webhook_public_key_count ?? 0);
setTelnyxCount(res.data?.telnyx_missing_webhook_public_key_count ?? 0);
setVonageCount(res.data?.vonage_missing_signature_secret_count ?? 0);
} catch {
setCount(0);
setTelnyxCount(0);
setVonageCount(0);
} finally {
setLoading(false);
}
@ -53,7 +58,8 @@ export function TelephonyConfigWarningsProvider({ children }: { children: ReactN
return (
<TelephonyConfigWarningsContext.Provider
value={{
telnyxMissingWebhookPublicKeyCount: count,
telnyxMissingWebhookPublicKeyCount: telnyxCount,
vonageMissingSignatureSecretCount: vonageCount,
refresh,
loading,
}}