# Handle incoming calls
In the previous section you added the ability to initiate a call. This section describes how to get notified of and handle incoming calls.
If you have not yet initiated a call, follow the steps in [Make an audio call](/docs/in-app-calling/getting-started/javascript/make-call).
## Listen for incoming calls
1. In the `call-container` of `index.html`, add the following HTML:
```html
```
2. Inside the `SinchClientWrapper` `#sinchClientListener` `onClientStarted` callback, add a listener for incoming calls:
```javascript
class SinchClientWrapper {
// ...
#sinchClientListener() {
return {
// ...
onClientStarted: (sinchClient) => {
console.log("Sinch - Start client succeded");
const { callClient } = sinchClient;
callClient.addListener({
onIncomingCall: (client, call) => {
this.ui.onIncomingCall(call);
this.#callListeners(call);
},
});
},
};
}
}
```
3. Inside the `UI` class, add functions to handle incoming calls and show/hide **Answer** and **Hang Up** buttons:
```javascript
class UI {
// ...
onIncomingCall(call) {
console.log("Incoming call", call);
this.audio.srcObject = call.incomingStream;
this.onAnswer = this.#handleCall("answer", call, this.onAnswer);
this.onHangup = this.#handleCall("hangup", call, this.onHangup);
}
onCallEstablished(call) {
console.log("Call established", call);
this.#hideElement("answer");
this.#showElement("hangup");
}
onCallEnded(call) {
console.log("Call ended", call);
this.#hideElement("answer");
this.#hideElement("hangup");
}
#handleCall(id, call, oldOnClickFunction) {
const element = document.getElementById(id);
element.removeEventListener("click", oldOnClickFunction);
element.style = "display: block";
const onClickFunction = () => call[id]();
element.addEventListener("click", onClickFunction);
return onClickFunction;
}
// ...
}
```
4. If you followed all the steps, the files should look like this:
`index.html`
```html
```
`index.js`
```javascript
const APP_KEY = "enter-application-key";
const APP_SECRET = "enter-application-secret";
const ENVIRONMENT_HOST = "ocra.api.sinch.com";
class SinchClientWrapper {
constructor(userId, ui) {
this.userId = userId;
this.ui = ui;
const sinchClient = Sinch.getSinchClientBuilder()
.applicationKey(APP_KEY)
.userId(userId)
.environmentHost(ENVIRONMENT_HOST)
.build();
sinchClient.addListener(this.#sinchClientListener());
sinchClient.setSupportManagedPush();
sinchClient.start();
this.sinchClient = sinchClient;
}
async makeCall(callee) {
const call = await this.sinchClient.callClient.callUser(callee);
this.#callListeners(call);
return call;
}
#callListeners(currentCall) {
currentCall.addListener({
onCallProgressing: (call) => {
this.ui.onCallProgressing(call);
},
onCallEstablished: (call) => {
this.ui.onCallEstablished(call);
},
onCallEnded: (call) => {
this.ui.onCallEnded(call);
},
});
}
#sinchClientListener() {
return {
onClientStarted: (sinchClient) => {
console.log("Sinch - Start client succeded");
const { callClient } = sinchClient;
callClient.addListener({
onIncomingCall: (client, call) => {
this.ui.onIncomingCall(call);
this.#callListeners(call);
},
});
},
onClientFailed: (sinchClient, error) => {
console.log("Sinch - Start client failed");
console.error(error);
},
/**
* The recommended way to implement this authentication scheme
* is that the Application Secret should be kept securely on your
* server-side backend, the signed token should be created and
* signed on your server, then passed via a secure channel to
* the application instance and Sinch client running on a device.
*/
onCredentialsRequired: (sinchClient, clientRegistration) => {
new JWT(APP_KEY, APP_SECRET, this.userId)
.toJwt()
.then(clientRegistration.register)
.catch((error) => {
clientRegistration.registerFailed();
console.error(error);
});
},
};
}
}
class UI {
constructor() {
this.#handleLogin();
this.audio = new Audio();
this.audio.autoplay = true;
console.log("UI started");
}
onIncomingCall(call) {
console.log("Incoming call", call);
this.audio.srcObject = call.incomingStream;
this.onAnswer = this.#handleCall("answer", call, this.onAnswer);
this.onHangup = this.#handleCall("hangup", call, this.onHangup);
}
onCallProgressing(call) {
console.log("Call progressing", call);
}
onCallEstablished(call) {
console.log("Call established", call);
this.#hideElement("answer");
this.#showElement("hangup");
}
onCallEnded(call) {
console.log("Call ended", call);
this.#hideElement("answer");
this.#hideElement("hangup");
}
#handleCall(id, call, oldOnClickFunction) {
const element = document.getElementById(id);
element.removeEventListener("click", oldOnClickFunction);
element.style = "display: block";
const onClickFunction = () => call[id]();
element.addEventListener("click", onClickFunction);
return onClickFunction;
}
#hideElement(id) {
const element = document.getElementById(id);
element.style = "display: none";
}
#showElement(id) {
const element = document.getElementById(id);
element.style = "display: block";
}
#handleLogin() {
document.getElementById("login").addEventListener("click", () => {
const userId = document.getElementById("userid").value;
this.#hideElement("login-container");
this.sinchClientWrapper = new SinchClientWrapper(userId, this);
this.#handleMakeCallClick();
this.#showElement("call-container");
});
}
#handleMakeCallClick() {
document.getElementById("call").addEventListener("click", async () => {
const callee = document.getElementById("callee").value;
const call = await this.sinchClientWrapper.makeCall(callee);
this.audio.srcObject = call.incomingStream;
this.onHangup = this.#handleCall("hangup", call, this.onHangup);
});
}
}
new UI();
```
5. Open two tabs and log in as two different users. Enter the second user ID as the callee in the first tab and press **Call**. Click **Answer** and you should see `Call established` in the console and hear audio confirmation. Press **Hang Up** to end or decline a call.
## Next steps
Now that you've built a simple app to make and receive calls, learn more about the [JavaScript SDK](https://developers.sinch.com/docs/in-app-calling/js-cloud/).