# 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/).