Skip to content

Add health checks

TwilioHealthIndicator reports whether the Twilio API is reachable and your credentials are still accepted, as a Terminus health indicator.

@nestjs/terminus is an optional peer dependency. Install it only if you want health checks:

Terminal window
npm install @nestjs/terminus

The indicator is exported from the nestjs-twilio/terminus subpath, not from the package root:

import { TwilioHealthIndicator } from 'nestjs-twilio/terminus';
src/health/health.module.ts
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { TwilioHealthIndicator } from 'nestjs-twilio/terminus';
import { HealthController } from './health.controller';
@Module({
imports: [TerminusModule],
controllers: [HealthController],
providers: [TwilioHealthIndicator],
})
export class HealthModule {}
src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
import { TwilioHealthIndicator } from 'nestjs-twilio/terminus';
@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly twilio: TwilioHealthIndicator
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([() => this.twilio.isHealthy('twilio').withTimeout(5000)]);
}
}

A healthy response:

{
"status": "ok",
"info": {
"twilio": {
"status": "up",
"accountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"friendlyName": "My Twilio Account",
"responseTime": 143
}
},
"error": {},
"details": { "twilio": { "status": "up", "responseTime": 143 } }
}

It fetches the client’s own Account resource, which exercises both network reachability and credential validity in a single call.

The indicator reports down when:

  • the request fails: network error, invalid credentials, Twilio outage
  • the account status is not active, i.e. suspended or closed

A suspended account still authenticates but cannot send anything, so treating it as healthy would be misleading.

Pass a client as the second argument to probe a client registered with registerClient():

@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly twilio: TwilioHealthIndicator,
@InjectTwilio('billing') private readonly billing: TwilioClient
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.twilio.isHealthy('twilio').withTimeout(5000),
() => this.twilio.isHealthy('twilio-billing', this.billing).withTimeout(5000),
]);
}
}

With no argument the default client from forRoot() is used. If no client is registered at all, the indicator reports down with guidance rather than throwing.

isHealthy() returns Terminus’ attempt builder, so withTimeout() and cacheFor() chain as they do for any indicator.

Every Twilio health check is a real API request. Use cacheFor() if your orchestrator probes frequently:

() => this.twilio.isHealthy('twilio').withTimeout(5000).cacheFor(30_000);

The indicator is built on Terminus’ attempt(), not up()/down(). That distinction matters: an indicator written with up()/down() that throws aborts the entire health check and returns 500, whereas attempt() translates a thrown error into a down result and the expected 503.

With invalid credentials you get:

{
"status": "error",
"error": {
"twilio": {
"status": "down",
"message": "Authentication Error - invalid username",
"responseTime": 412
}
}
}