001/*
002 *   Copyright 2024 Vonage
003 *
004 *   Licensed under the Apache License, Version 2.0 (the "License");
005 *   you may not use this file except in compliance with the License.
006 *   You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *   Unless required by applicable law or agreed to in writing, software
011 *   distributed under the License is distributed on an "AS IS" BASIS,
012 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *   See the License for the specific language governing permissions and
014 *   limitations under the License.
015 */
016package com.vonage.client.voice;
017
018import com.vonage.client.*;
019import com.vonage.client.auth.JWTAuthMethod;
020import com.vonage.client.common.HttpMethod;
021import com.vonage.client.voice.ncco.InputMode;
022import com.vonage.client.voice.ncco.Ncco;
023import com.vonage.jwt.Jwt;
024import java.io.IOException;
025import java.net.URI;
026import java.nio.file.Files;
027import java.nio.file.Path;
028import java.util.Objects;
029import java.util.function.Function;
030
031/**
032 * A client for talking to the Vonage Voice API. The standard way to obtain an instance of this class is to use {@link
033 * VonageClient#getVoiceClient()}.
034 */
035public class VoiceClient {
036    final RestEndpoint<Call, CallEvent> createCall;
037    final RestEndpoint<String, CallInfo> getCall;
038    final RestEndpoint<CallsFilter, CallInfoPage> listCalls;
039    final RestEndpoint<ModifyCallPayload, Void> modifyCall;
040    final RestEndpoint<StreamPayload, StreamResponse> startStream;
041    final RestEndpoint<String, StreamResponse> stopStream;
042    final RestEndpoint<TalkPayload, TalkResponse> startTalk;
043    final RestEndpoint<String, TalkResponse> stopTalk;
044    final RestEndpoint<DtmfPayload, DtmfResponse> sendDtmf;
045    final RestEndpoint<AddDtmfListenerRequest, Void> addDtmfListener;
046    final RestEndpoint<String, Void> removeDtmfListener;
047    final RestEndpoint<String, byte[]> downloadRecording;
048
049    /**
050     * Constructor.
051     *
052     * @param wrapper (required) shared HTTP wrapper object used for making REST calls.
053     */
054    public VoiceClient(HttpWrapper wrapper) {
055
056        @SuppressWarnings("unchecked")
057        class Endpoint<T, R> extends DynamicEndpoint<T, R> {
058            Endpoint(Function<T, String> pathGetter, HttpMethod method, R... type) {
059                super(DynamicEndpoint.<T, R> builder(type).authMethod(JWTAuthMethod.class)
060                        .responseExceptionType(VoiceResponseException.class)
061                        .requestMethod(method).wrapper(wrapper).pathGetter((de, req) -> {
062                            String base = de.getHttpWrapper().getHttpConfig().getVersionedApiBaseUri("v1");
063                            String path = pathGetter.apply(req);
064                            if (path.startsWith("http") && method == HttpMethod.GET) {
065                                return path;
066                            }
067                            return base + "/calls" + (path.isEmpty() ? "" : "/" + path);
068                        })
069                );
070            }
071        }
072
073        createCall = new Endpoint<>(req -> "", HttpMethod.POST);
074        getCall = new Endpoint<>(Function.identity(), HttpMethod.GET);
075        listCalls = new Endpoint<>(req -> "", HttpMethod.GET);
076        modifyCall = new Endpoint<>(req -> req.uuid, HttpMethod.PUT);
077        startStream = new Endpoint<>(req -> req.uuid + "/stream", HttpMethod.PUT);
078        stopStream = new Endpoint<>(uuid -> uuid + "/stream", HttpMethod.DELETE);
079        startTalk = new Endpoint<>(req -> req.uuid + "/talk", HttpMethod.PUT);
080        stopTalk = new Endpoint<>(uuid -> uuid + "/talk", HttpMethod.DELETE);
081        sendDtmf = new Endpoint<>(req -> req.uuid + "/dtmf", HttpMethod.PUT);
082        addDtmfListener = new Endpoint<>(req -> req.uuid + "/input/dtmf", HttpMethod.PUT);
083        removeDtmfListener = new Endpoint<>(uuid -> uuid + "/input/dtmf", HttpMethod.DELETE);
084        downloadRecording = new Endpoint<>(Function.identity(), HttpMethod.GET);
085    }
086
087    private String validateUuid(String uuid) {
088        return Objects.requireNonNull(uuid, "UUID is required.");
089    }
090
091    private String validateUrl(String url) {
092        if (url == null || url.trim().isEmpty()) {
093            throw new IllegalArgumentException("URL is required.");
094        }
095        return URI.create(url).toString();
096    }
097
098    /**
099     * Begin a call to a phone number.
100     *
101     * @param callRequest Describing the call to be made.
102     *
103     * @return A CallEvent describing the initial state of the call, containing the {@code uuid} required to interact
104     * with the ongoing phone call.
105     *
106     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
107     * @throws VonageResponseParseException if the response from the API could not be parsed.
108     */
109    public CallEvent createCall(Call callRequest) throws VonageResponseParseException, VonageClientException {
110        return createCall.execute(callRequest);
111    }
112
113    /**
114     * Obtain the first page of CallInfo objects, representing the most recent calls initiated by {@link
115     * #createCall(Call)}.
116     *
117     * @return A CallInfoPage representing the response from the Vonage Voice API.
118     *
119     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
120     * @throws VonageResponseParseException if the response from the API could not be parsed.
121     */
122    public CallInfoPage listCalls() throws VonageResponseParseException, VonageClientException {
123        return listCalls(null);
124    }
125
126    /**
127     * Obtain the first page of CallInfo objects matching the query described by {@code filter}, representing the most
128     * recent calls initiated by {@link #createCall(Call)}.
129     *
130     * @param filter (optional) A filter describing which calls to be listed.
131     *
132     * @return A CallInfoPage representing the response from the Vonage Voice API.
133     *
134     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
135     * @throws VonageResponseParseException if the response from the API could not be parsed.
136     */
137    public CallInfoPage listCalls(CallsFilter filter) throws VonageResponseParseException, VonageClientException {
138        return listCalls.execute(filter);
139    }
140
141    /**
142     * Look up the status of a single call initiated by {@link #createCall(Call)}.
143     *
144     * @param uuid (required) The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
145     *             This value can be obtained with {@link CallEvent#getUuid()}.
146     *
147     * @return A CallInfo object, representing the response from the Vonage Voice API.
148     *
149     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
150     * @throws VonageResponseParseException if the response from the API could not be parsed.
151     */
152    public CallInfo getCallDetails(String uuid) throws VonageResponseParseException, VonageClientException {
153        return getCall.execute(validateUuid(uuid));
154    }
155
156    /**
157     * Send DTMF codes to an ongoing call.
158     *
159     * @param uuid   (REQUIRED) The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
160     *               This value can be obtained with {@link CallEvent#getUuid()}
161     * @param digits (REQUIRED) A string specifying the digits to be sent to the call. Valid characters are the digits
162     *               {@code 1-9</tt>, <tt>#</tt>, <tt>*</tt>, with the special character <tt>p} indicating a short pause
163     *               between tones.
164     *
165     * @return A CallInfo object, representing the response from the Vonage Voice API.
166     *
167     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
168     * @throws VonageResponseParseException if the response from the API could not be parsed.
169     */
170    public DtmfResponse sendDtmf(String uuid, String digits) throws VonageResponseParseException, VonageClientException {
171        return sendDtmf.execute(new DtmfPayload(digits, validateUuid(uuid)));
172    }
173
174    private void modifyCall(String callId, ModifyCallAction action) throws VoiceResponseException {
175        modifyCall.execute(new ModifyCallPayload(action, validateUuid(callId)));
176    }
177
178    /**
179     * Modify a call with the {@linkplain ModifyCallAction#EARMUFF} action.
180     * This prevents the call from hearing audio.
181     *
182     * @param callId UUID of the call.
183     *
184     * @throws VoiceResponseException If there was an error performing the action.
185     *
186     * @since 7.11.0
187     */
188    public void earmuffCall(String callId) throws VoiceResponseException {
189        modifyCall(callId, ModifyCallAction.EARMUFF);
190    }
191
192    /**
193     * Modify a call with the {@linkplain ModifyCallAction#UNEARMUFF} action.
194     * This allows the call to hear audio again.
195     *
196     * @param callId UUID of the call.
197     *
198     * @throws VoiceResponseException If there was an error performing the action.
199     *
200     * @since 7.11.0
201     */
202    public void unearmuffCall(String callId) throws VoiceResponseException {
203        modifyCall(callId, ModifyCallAction.UNEARMUFF);
204    }
205
206    /**
207     * Modify a call with the {@linkplain ModifyCallAction#MUTE} action.
208     *
209     * @param callId UUID of the call.
210     *
211     * @throws VoiceResponseException If there was an error performing the action.
212     *
213     * @since 7.11.0
214     */
215    public void muteCall(String callId) throws VoiceResponseException {
216        modifyCall(callId, ModifyCallAction.MUTE);
217    }
218
219    /**
220     * Modify a call with the {@linkplain ModifyCallAction#UNMUTE} action.
221     *
222     * @param callId UUID of the call.
223     *
224     * @throws VoiceResponseException If there was an error performing the action.
225     *
226     * @since 7.11.0
227     */
228    public void unmuteCall(String callId) throws VoiceResponseException {
229        modifyCall(callId, ModifyCallAction.UNMUTE);
230    }
231
232    /**
233     * Modify a call with the {@linkplain ModifyCallAction#HANGUP} action.
234     *
235     * @param callId UUID of the call.
236     *
237     * @throws VoiceResponseException If there was an error performing the action.
238     *
239     * @since 7.11.0
240     */
241    public void terminateCall(String callId) throws VoiceResponseException {
242        modifyCall(callId, ModifyCallAction.HANGUP);
243    }
244
245    /**
246     * Transfer a call to a different NCCO endpoint.
247     *
248     * @param uuid    The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value
249     *                can be obtained with {@link CallEvent#getUuid()}.
250     * @param nccoUrl The URL of the NCCO endpoint the call should be transferred to.
251     *
252     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
253     * @throws VonageResponseParseException if the response from the API could not be parsed.
254     */
255    public void transferCall(String uuid, String nccoUrl) throws VonageResponseParseException, VonageClientException {
256        modifyCall.execute(new TransferCallPayload(validateUrl(nccoUrl), validateUuid(uuid)));
257    }
258
259    /**
260     * Transfer a call to a different NCCO.
261     *
262     * @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value can
263     *             be obtained with {@link CallEvent#getUuid()}.
264     * @param ncco The new NCCO that will be used in the call.
265     *
266     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
267     * @throws VonageResponseParseException if the response from the API could not be parsed.
268     */
269    public void transferCall(String uuid, Ncco ncco) throws VonageResponseParseException, VonageClientException {
270        modifyCall.execute(new TransferCallPayload(ncco, validateUuid(uuid)));
271    }
272
273    /**
274     * Stream audio to an ongoing call.
275     *
276     * @param uuid      The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value
277     *                  can be obtained with {@link CallEvent#getUuid()}.
278     * @param streamUrl A URL of an audio file in MP3 or 16-bit WAV format, to be streamed to the call.
279     * @param loop      The number of times to repeat the audio. The default value is {@code 1}, or you can use {@code
280     *                  0} to indicate that the audio should be repeated indefinitely.
281     *
282     * @return The data returned from the Voice API.
283     *
284     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
285     * @throws VonageResponseParseException if the response from the API could not be parsed.
286     */
287    public StreamResponse startStream(String uuid, String streamUrl, int loop) throws VonageResponseParseException, VonageClientException {
288        return startStream(uuid, streamUrl, loop, 0d);
289    }
290
291    /**
292     * Stream audio to an ongoing call.
293     *
294     * @param uuid      The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value
295     *                  can be obtained with {@link CallEvent#getUuid()}.
296     * @param streamUrl A URL of an audio file in MP3 or 16-bit WAV format, to be streamed to the call.
297     * @param loop      The number of times to repeat the audio. The default value is {@code 1}, or you can use {@code
298     *                  0} to indicate that the audio should be repeated indefinitely.
299     * @param level The audio level of the stream, between -1 and 1 with a precision of 0.1. The default value is 0.
300     *
301     * @return The data returned from the Voice API.
302     *
303     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
304     * @throws VonageResponseParseException if the response from the API could not be parsed.
305     *
306     * @since 7.3.0
307     */
308    public StreamResponse startStream(String uuid, String streamUrl, int loop, double level) throws VonageResponseParseException, VonageClientException {
309        return startStream.execute(new StreamPayload(validateUrl(streamUrl), loop, level, validateUuid(uuid)));
310    }
311
312    /**
313     * Stream audio to an ongoing call.
314     *
315     * @param uuid      The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value
316     *                  can be obtained with {@link CallEvent#getUuid()}.
317     * @param streamUrl A URL of an audio file in MP3 or 16-bit WAV format, to be streamed to the call.
318     *
319     * @return The data returned from the Voice API.
320     *
321     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
322     * @throws VonageResponseParseException if the response from the API could not be parsed.
323     */
324    public StreamResponse startStream(String uuid, String streamUrl) throws VonageResponseParseException, VonageClientException {
325        return startStream(uuid, streamUrl, 1);
326    }
327
328    /**
329     * Stop the audio being streamed into a call.
330     *
331     * @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value can
332     *             be obtained with {@link CallEvent#getUuid()}.
333     *
334     * @return The data returned from the Voice API.
335     *
336     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
337     * @throws VonageResponseParseException if the response from the API could not be parsed.
338     */
339    public StreamResponse stopStream(String uuid) throws VonageResponseParseException, VonageClientException {
340        return stopStream.execute(validateUuid(uuid));
341    }
342
343    /**
344     * Send a synthesized speech message to an ongoing call.
345     * @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
346     * This value can be obtained with {@link CallEvent#getUuid()}.
347     *
348     * @param properties Properties of the text-to-speech request.
349     *
350     * @return Metadata from the Voice API if successful.
351     *
352     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
353     * @throws VonageResponseParseException if the response from the API could not be parsed.
354     *
355     * @since 7.3.0
356     */
357    public TalkResponse startTalk(String uuid, TalkPayload properties) {
358        Objects.requireNonNull(properties, "TalkPayload is required").uuid = validateUuid(uuid);
359        return startTalk.execute(properties);
360    }
361
362    /**
363     * Send a synthesized speech message to an ongoing call.
364     * <p>
365     * The message will only play once, spoken with the default en-US voice.
366     *
367     * @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value can
368     *             be obtained with {@link CallEvent#getUuid()}
369     * @param text The message to be spoken to the call participants.
370     *
371     * @return The data returned from the Voice API.
372     *
373     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
374     * @throws VonageResponseParseException if the response from the API could not be parsed.
375     *
376     * @deprecated Use {@link #startTalk(String, TalkPayload)}.
377     */
378    @Deprecated
379    public TalkResponse startTalk(String uuid, String text) throws VonageResponseParseException, VonageClientException {
380        return startTalk(uuid, TalkPayload.builder(text).build());
381    }
382
383    /**
384     * @param uuid      The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value
385     *                   can be obtained with {@link CallEvent#getUuid()}
386     * @param text       The message to be spoken to the call participants.
387     *
388     * @param language  The Language to use when converting text-to-speech.
389     *
390     * @return The data returned from the Voice API.
391     *
392     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
393     * @throws VonageResponseParseException if the response from the API could not be parsed.
394     *
395     * @deprecated Use {@link #startTalk(String, TalkPayload)}.
396     */
397    @Deprecated
398    public TalkResponse startTalk(String uuid, String text, TextToSpeechLanguage language) throws VonageResponseParseException, VonageClientException {
399        return startTalk(uuid, TalkPayload.builder(text).language(language).build());
400    }
401
402    /**
403     * Send a synthesized speech message to an ongoing call.
404     * <p>
405     * The message will be spoken with the default en-US voice.
406     *
407     * @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value can
408     *             be obtained with {@link CallEvent#getUuid()}.
409     * @param text The message to be spoken to the call participants.
410     * @param loop The number of times to repeat the message. The default value is {@code 1}, or you can use {@code 0}
411     *             to indicate that the message should be repeated indefinitely.
412     *
413     * @return The data returned from the Voice API.
414     *
415     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
416     * @throws VonageResponseParseException if the response from the API could not be parsed.
417     *
418     * @deprecated Use {@link #startTalk(String, TalkPayload)}.
419     */
420    @Deprecated
421    public TalkResponse startTalk(String uuid, String text, int loop) throws VonageResponseParseException, VonageClientException {
422        return startTalk(uuid, TalkPayload.builder(text).loop(loop).build());
423    }
424
425    /**
426     * Send a synthesized speech message to an ongoing call.
427     *
428     * @param uuid      The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
429     *                  This value can be obtained with {@link CallEvent#getUuid()}.
430     * @param text      The message to be spoken to the call participants.
431     * @param language  The language to use for the text-to-speech.
432     * @param style     The language style to use for the text-to-speech.
433     * @param loop      The number of times to repeat the message. The default value is {@code 1}, or you can use {@code
434     *                  0} to indicate that the message should be repeated indefinitely.
435     *
436     * @return The data returned from the Voice API.
437     *
438     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
439     * @throws VonageResponseParseException if the response from the API could not be parsed.
440     *
441     * @deprecated Use {@link #startTalk(String, TalkPayload)}.
442     */
443    @Deprecated
444    public TalkResponse startTalk(String uuid, String text, TextToSpeechLanguage language, int style, int loop) throws VonageResponseParseException, VonageClientException {
445        return startTalk(uuid, TalkPayload.builder(text).loop(loop).language(language).style(style).build());
446    }
447
448    /**
449     * Send a synthesized speech message to an ongoing call.
450     *
451     * @param uuid      The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value
452     *                  can be obtained with {@link CallEvent#getUuid()}.
453     * @param text      The message to be spoken to the call participants.
454     * @param language  The language to use for the text-to-speech.
455     * @param style     The language style to use for the text-to-speech.
456     *
457     * @return The data returned from the Voice API.
458     *
459     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
460     * @throws VonageResponseParseException if the response from the API could not be parsed.
461     *
462     * @deprecated Use {@link #startTalk(String, TalkPayload)}.
463     */
464    @Deprecated
465    public TalkResponse startTalk(String uuid, String text, TextToSpeechLanguage language, int style) throws VonageResponseParseException, VonageClientException {
466        return startTalk(uuid, TalkPayload.builder(text).language(language).style(style).build());
467    }
468
469    /**
470     * Stop the message being spoken into a call.
471     *
472     * @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value can
473     *             be obtained with {@link CallEvent#getUuid()}.
474     *
475     * @return The data returned from the Voice API.
476     *
477     * @throws VonageClientException        if there was a problem with the Vonage request or response objects.
478     * @throws VonageResponseParseException if the response from the API could not be parsed.
479     */
480    public TalkResponse stopTalk(String uuid) throws VonageResponseParseException, VonageClientException {
481        return stopTalk.execute(validateUuid(uuid));
482    }
483
484    /**
485     * Register a listener for asynchronous DTMF events sent by a caller to an
486     * {@linkplain com.vonage.client.voice.ncco.InputAction} NCCO action, when the
487     * {@linkplain com.vonage.client.voice.ncco.InputAction.Builder#mode(InputMode)} is
488     * {@link com.vonage.client.voice.ncco.InputMode#ASYNCHRONOUS}.
489     *
490     * @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
491     * This value can be obtained with {@link CallEvent#getUuid()}.
492     *
493     * @param eventUrl The URL to send asynchronous DTMF user input events to.
494     *
495     * @throws VoiceResponseException If the call does not exist or the listener could not be added,
496     * for example if the call's state or input mode are incompatible.
497     *
498     * @since 8.12.0
499     */
500    public void addDtmfListener(String uuid, String eventUrl) throws VoiceResponseException {
501        addDtmfListener.execute(new AddDtmfListenerRequest(validateUuid(uuid), URI.create(validateUrl(eventUrl))));
502    }
503
504    /**
505     * Stop receiving updates for asynchronous DTMF events sent by a caller to an
506     * {@linkplain com.vonage.client.voice.ncco.InputAction} NCCO, when the
507     * {@linkplain com.vonage.client.voice.ncco.InputAction.Builder#mode(InputMode)} is
508     * {@link com.vonage.client.voice.ncco.InputMode#ASYNCHRONOUS}. Calling this method
509     * stops sending DTMF events to the event URL set in {@link #addDtmfListener(String, String)}.
510     *
511     * @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
512     * This value can be obtained with {@link CallEvent#getUuid()}.
513     *
514     * @throws VoiceResponseException If the call does not exist or have a listener attached.
515     *
516     * @since 8.12.0
517     */
518    public void removeDtmfListener(String uuid) throws VoiceResponseException {
519        removeDtmfListener.execute(validateUuid(uuid));
520    }
521
522    /**
523     * Download a recording.
524     *
525     * @param recordingUrl The recording URL, as obtained from the webhook callback.
526     *
527     * @return The raw contents of the downloaded recording as a byte array.
528     *
529     * @throws IllegalArgumentException If the recordingUrl is invalid.
530     * @throws VoiceResponseException If there was an error downloading the recording from the URL.
531     *
532     * @since 7.11.0
533     */
534    public byte[] downloadRecordingRaw(String recordingUrl) {
535        return downloadRecording.execute(validateUrl(recordingUrl));
536    }
537
538    /**
539     * Download a recording and save it to a file.
540     *
541     * @param recordingUrl The recording URL, as obtained from the webhook callback.
542     * @param destination Path to save the recording to.
543     *
544     * @throws IOException If there was an error writing to the file.
545     * @throws VoiceResponseException If there was an error downloading the recording from the URL.
546     * @throws IllegalArgumentException If the recordingUrl is invalid.
547     *
548     * @since 7.11.0
549     */
550    public void saveRecording(String recordingUrl, Path destination) throws IOException {
551        Path path = Objects.requireNonNull(destination, "Save path is required.");
552        byte[] binary = downloadRecordingRaw(recordingUrl);
553        if (Files.isDirectory(destination)) {
554            String fileName = recordingUrl.substring(recordingUrl.lastIndexOf('/') + 1);
555            path = path.resolve(fileName);
556        }
557        Files.write(path, binary);
558    }
559
560    /**
561     * Utility method for verifying whether a token was signed by a secret.
562     * This is mostly useful when using signed callbacks to ensure that the inbound
563     * data came from Vonage servers. The signature is performed using the SHA-256 HMAC algorithm.
564     *
565     * @param jwt The JSON Web Token to verify.
566     * @param secret The symmetric secret key (HS256) to use for decrypting the token's signature.
567     *
568     * @return {@code true} if the token was signed by the secret, {@code false} otherwise.
569     *
570     * @since 7.11.0
571     */
572    public static boolean verifySignature(String jwt, String secret) {
573        return Jwt.verifySignature(jwt, secret);
574    }
575}