How to Implement Call Forwarding in Go

January 05, 2026
Written by
Popoola Temitope
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

How to Implement Call Forwarding in Go

Call forwarding is a valuable telecommunications feature that enables the redirection of incoming calls to another phone number.

A major advantage of call forwarding is its ability to ensure that no customer inquiry goes unanswered, even when the primary recipient is unavailable. This feature enhances accessibility, improves customer service, and ensures that calls are directed to the appropriate person or team, ultimately boosting overall business efficiency.

With Twilio’s Voice API, implementing call forwarding in a Go application becomes straightforward by using TwiML (Twilio Markup Language) and Twilio's Voice API.

Twilio's Voice API allows developers to manage and control voice calls programmatically. And the TwiML <Dial> verb forwards incoming calls to another phone number. This is done in real time through webhooks that Twilio triggers whenever there is an incoming call to your Twilio phone number.

In this tutorial, you will learn how to forward an incoming Twilio call to a different phone number in a Go application.

Prerequisites

Before you get started, ensure you have the following requirements:

  • Go version 1.22 or above
  • Access to a MySQL database
  • A Twilio paid account is required to complete this tutorial. If you're new to Twilio, click here to create an account
  • Ngrok and a free ngrok account: This is for exposing the locally running Go application to the public internet, allowing Twilio's Call webhook to forward the calls in real-time
  • Two mobile/cell phone numbers that can make and receive calls
  • Your web browser of choice

Create a new project

We'll start off by creating a folder named call-forwarding for our Go project, change into it, and initialize a Go module, by running the following commands.

mkdir call-forwarding
cd call-forwarding
go mod init call-forwarding

Install the Go Gin package

To simplify creating an API endpoint for handling Twilio's incoming call webhooks, install the Go Gin package using the command below.

go get github.com/gin-gonic/gin github.com/twilio/twilio-go/twiml@v1.26.4

Setup the application's database

Now, log in to your MySQL server and create a new database named "callforwarding". Then, run the SQL query below to define the database's schema.

CREATE TABLE agents (
    id int(11) NOT NULL,
    phone varchar(20) NOT NULL
);

Next, to allow the Go application to connect to the database, install the MySQL Go package by running the following command in your application's terminal.

go get -u github.com/go-sql-driver/mysql

Create the call forwarding logic

Now, let’s create an API endpoint to handle incoming Twilio calls and forward them to another phone number. To do that, create a new file named main.go inside the project's top-leveldirectory and add the following code to the file:

package main

import (
	"database/sql"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"github.com/twilio/twilio-go/twiml"
)

var db *sql.DB

func main() {
	dsn := "<db_username>:<db_password>@tcp(localhost:3306)/callforwarding"
	var err error

	db, err = sql.Open("mysql", dsn)
	if err != nil {
		log.Fatalf("Failed to open database connection: %v", err)
	}
	defer db.Close()

	err = db.Ping()
	if err != nil {
		log.Fatalf("Failed to ping database: %v", err)
	}
	log.Println("Successfully connected to the database!")

	r := gin.Default()
	r.LoadHTMLGlob("templates/*")

	rand.Seed(time.Now().UnixNano())

	r.GET("/admin", func(c *gin.Context) {
		agents, err := getAllAgents()
		if err != nil {
			log.Printf("Error fetching agents for admin page: %v", err)
			c.HTML(http.StatusInternalServerError, "error.html", gin.H{"message": "Error fetching agent list."})
			return
		}

		success := c.Query("success")
		errorMsg := c.Query("error")

		c.HTML(http.StatusOK, "admin.html", gin.H{
			"AgentNumbers": agents,
			"Success":      success,
			"Error":        errorMsg,
		})
	})

	r.POST("/add-agent", func(c *gin.Context) {
		phone := c.PostForm("phone")

		if phone == "" {
			c.Redirect(http.StatusFound, "/admin?error=Phone+number+cannot+be+empty")
			return
		}

		err := addAgent(phone)
		if err != nil {
			log.Printf("Error adding agent '%s': %v", phone, err)
			c.Redirect(http.StatusFound, "/admin?error=Failed+to+add+agent")
			return
		}

		c.Redirect(http.StatusFound, "/admin?success=Agent+added+successfully")
	})

	r.POST("/incoming-call", handleIncomingCall)

	port := "8080"
	log.Printf("Server starting on port %s...", port)
	err = r.Run(":" + port)
	if err != nil {
		log.Fatalf("Failed to start server: %v", err)
	}
}

func handleIncomingCall(c *gin.Context) {
	agents, err := getAllAgents()
	if err != nil {
		log.Printf("Error fetching agents for incoming call: %v", err)
		c.String(http.StatusInternalServerError, "Internal Server Error")
		return
	}

	twimlElements := []twiml.Element{}
	if len(agents) == 0 {
		twimlElements = append(twimlElements, &twiml.VoiceSay{
			Message: "No agents are available at the moment. Please try again later.",
		})

		log.Println("No agents available.")
	} else {
		forwardTo := agents[rand.Intn(len(agents))]
		twimlElements = append(twimlElements,
			&twiml.VoiceSay{
				Message: "Please wait while we connect your call to an agent.",
			},
			&twiml.VoiceDial{
				Number: forwardTo,
			},
		)

		log.Printf("Forwarding call to: %s", forwardTo)
	}   
	twiml, _ := twiml.Voice(twimlElements)

	c.Header("Content-Type", "application/xml")
	c.String(http.StatusOK, twiml)
}

func addAgent(phone string) error {
	stmt, err := db.Prepare(`INSERT INTO agents (phone) VALUES (?)`)
	if err != nil {
		log.Printf("Prepare error: %v", err)
		return err
	}
	defer stmt.Close()

	_, err = stmt.Exec(phone)
	if err != nil {
		log.Printf("Exec error for phone %s: %v", phone, err)
	}
	return err
}

func getAllAgents() ([]string, error) {
	rows, err := db.Query(`SELECT phone FROM agents`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var agents []string
	for rows.Next() {
		var phone string
		if err := rows.Scan(&phone); err != nil {
			return nil, err
		}
		agents = append(agents, phone)
	}
	return agents, rows.Err()
}

With the code in place, replace <db_username>and <db_password> with the username and password for your database.

In the code above,

  • The main()function initializes the application by setting up the MySQL database connection and starting the web server using Gin
  • The "/incoming-call " route handles incoming calls and forwards the call to the available agent's phone number.
  • The "/admin" route retrieves all agent phone numbers from the database and renders the admin.html template with the list of agents.

Create the admin interface

Let’s create the application template where the admin can enter an agent's support phone number to enable the easy forwarding of incoming customer calls. To set up the admin interface, create a folder named templates and within it create an HTML file named admin.html. Then, add the following content to the file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Admin Panel</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container py-5">
        <h1 class="mb-4 text-center">Admin Panel</h1>

        {{if .Success}}
        <div class="alert alert-success alert-dismissible fade show" role="alert">
            {{.Success}}
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
        </div>
        {{end}}

        {{if .Error}}
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            {{.Error}}
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
        </div>
        {{end}}

        <div class="card mb-4">
            <div class="card-body">
                <form action="/add-agent" method="POST">
                    <div class="mb-3">
                        <label for="phone" class="form-label">Agent Phone Number:</label>
                        <input type="text" id="phone" name="phone" class="form-control" required>
                    </div>
                    <button type="submit" class="btn btn-primary">Add Agent</button>
                </form>
            </div>
        </div>

        <h2 class="mb-3">Current Agents</h2>
        <ul class="list-group">
            {{range .AgentNumbers}}
            <li class="list-group-item">{{.}}</li>
            {{else}}
            <li class="list-group-item text-muted">No agents available.</li>
            {{end}}
        </ul>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

Make the application accessible over the internet

Now, let’s start the application and make the endpoint accessible over the internet using ngrok. To do this, open another terminal session and run the command below.

ngrok http 8080

The command above will generate a Forwarding URL, as shown in the screenshot below. Keep it handy, as you'll need it in just a moment.

Terminal window displaying ngrok session details including status, version, region, and forwarding URL.

Configure the Twilio webhook for incoming calls

Let’s configure Twilio to send incoming call requests to our application endpoint via Twilio's incoming webhook. To do this, from your Twilio Console dashboard, navigate to Phone Numbers > Manage > Active Numbers, select your Twilio number, and click the Configure tab, as shown in the screenshot below.

Interface showing Twilio voice configuration with settings for call handling and emergency calling.

Then, in the Voice Configuration section, set up the incoming call with the following settings.

  • A Call Comes In: Select "Webhook"
  • URL: Paste the generated ngrok Forwarding URL and append "/incoming-call" at the end
  • HTTP Method: Select "HTTP POST".

Then click on the Save configuration button, at the bottom of the page, to save the configuration.

Test the call forwarding

To test the application, you first need to start the application by running the command below.

go run main.go

You should see output similar to the following printed to the terminal:

2025/07/30 12:11:33 Successfully connected to the database!
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)
[GIN-debug] Loaded HTML Templates (2): 
	- 
	- admin.html
[GIN-debug] GET    /admin                    --> main.main.func1 (3 handlers)
[GIN-debug] POST   /add-agent                --> main.main.func2 (3 handlers)
[GIN-debug] POST   /incoming-call            --> main.handleIncomingCall (3 handlers)
2025/07/30 12:11:33 Server starting on port 8080...
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
[GIN] 2025/07/30 - 12:12:09 | 200 |   13.851791ms |             ::1 | GET      "/admin"
[GIN] 2025/07/30 - 12:12:09 | 404 |         917ns |             ::1 | GET      "/apple-touch-icon-precomposed.png"
[GIN] 2025/07/30 - 12:12:09 | 404 |         834ns |             ::1 | GET      "/favicon.ico"
[GIN] 2025/07/30 - 12:12:09 | 404 |         958ns |             ::1 | GET      "/apple-touch-icon.png

Now, open "http://localhost:8080/admin" in your browser. Then, add your agent's phone number, as shown in the screenshot below, so that incoming calls from your users will be forwarded to that number.

Admin panel for adding agent phone numbers with a current agent listed.

Next, call your Twilio number. Once the call reaches your endpoint, you will hear the message, "Please wait while we connect your call to an agent". After the message, your call will be forwarded to one of the agent's phone numbers.

That's how to build a Twilio call forwarding using Go

Call forwarding is an essential feature for businesses, facilitating seamless communication by redirecting incoming calls to the appropriate department or team member. It ensures that no customer inquiry goes unanswered, improves response times, and enhances overall customer satisfaction.

In this tutorial, you learned how to implement call forwarding using Twilio and Go. You set up an endpoint that responds to Twilio's incoming calls and connect the application to Twilio's webhook to handle incoming calls effectively.

Popoola Temitope is a mobile developer and a technical writer who loves writing about frontend technologies. He can be reached on LinkedIn.

Call forwarding icons created by Flat-icons-com on Flaticon.