Events API

Overview

The Events API exposes events that happen inside Talkdesk® as webhook calls. It allows subscribing to events around an app's lifecycle (installations, updates, and trial expiration alerts).

📘

Use Case

During the lifecycle of a partner app installation, several events are emitted by the Events API. These events are the result of certain actions performed by a customer, to which you can respond by approving or rejecting each of them. These actions can be the installation of your app in the customer's account, a change to the users or subscription plan in an existing installation, etc.

👍

Access and Registration

Authentication

Usage

Talkdesk allows you to subscribe to events around an app's lifecycle (including installations, updates, and trial expiration) when you are creating/updating an app version on Builder by providing an event's callback URL.

Your app will be notified about the events via HTTP requests.

Events

  • app.installed - emitted when a Talkdesk administrator installs the partner app from AppConnect.

  • app.updated - emitted when a Talkdesk administrator updates settings for the partner app on AppConnect.

  • app.uninstalled - emitted when a Talkdesk administrator uninstalls the partner app from AppConnect.

  • app.trial_started - emitted when a trial of the partner app officially starts. This will follow the app.installed event and the partner successful acknowledgement of the installation via Apps API.

  • app.trial_extended - emitted when a trial of the partner app is extended. As a reminder, there is no option for a customer to extend a trial in the UI. Once this request is processed by the Talkdesk team, the partner will receive this event.

  • app.trial_ended - emitted when a trial of the partner app has been expired by the system, or ended by the user in the event of app uninstallations or subscription changes.

  • app.credentials_rotated - emitted by Talkdesk as a routine security measure, acknowledging credential rotation, and/or whenever the original installation credentials are compromised.

  • user.logout - optional event emitted when a user belonging to an installation of a Talkdesk partner app has logged out.

📘

You can opt to subscribe to only one optional event (when a user logs out). All the other events are mandatory.

👍

Calls from Events API can be validated by leveraging the Talkdesk security mechanism.

Event Delivery

🚧

All events are delivered via HTTPS.

Talkdesk guarantees, at least once, semantics for event delivery. Although rarely, this means that you may receive the same event more than once.

All events contain a unique identifier in the event_id field. To avoid processing the same event more than once, Talkdesk advises verifying if the event was already received by looking at its event_id field and matching it to previously received events. This can be done by accessing the /apps/{app_id}/events/{id} endpoint.

Event Signatures

All webhook calls are made via HTTPS, ensuring transport-level security for Events API payloads.

Talkdesk signs all the events sent to your endpoint, allowing you to validate that they were not sent by a third party.

Signature Method

Signing is executed via asymmetric cryptography, using a single pair of public-private keys.

Talkdesk signs the events payload using a digital signature, utilizing a private key to sign the event payload of each request. You must use the public key to validate this signature.

The result is sent using the X-Hub-Ecdsa-Signature header. It also sends the X-Hub-Ecdsa-Signature-Id header that contains the ID of the key used for signing.

📘

Public Key

Make a GET request to /events/signature-keys/:id. This route is unauthenticated.

This public key must be used for the events' payload validation.

Attributes Returned by the Request

  • public_key (string).
  • key_id (string).
  • created_at (UTC date).
  • expires_at (UTC date).

Signature Delivery

The signature of the event payload is sent in the HTTP X-Hub-Ecdsa-Signature header.

Signature Verification
To verify the payload, Talkdesk has a few examples in some common languages.

How to Verify the Signature

1 - Extract the signature from the HTTP header X-Hub-Ecdsa-Signature and extract the key ID from the X-Hub-Ecdsa-Signature-Id header; fetch and validate the public key.

2 - Decode the extracted signature (hexadecimal-encoded) and the public key (base64-encoded).

3 - Verify the signature provided in the event using the decoded signature, the SHA512 algorithm, and the decoded public key.

📘

Web Framework

When using a web framework, you must ensure the string representation of the JSON body does not change field representations, such as dates, and does not include empty spaces.

const crypto = require('crypto')

// Request data (REPRESENTATION PURPOSES ONLY)
const requestHeaders = {
  "X-Hub-Ecdsa-Signature": "<signature-key-placeholder>",
  "X-Hub-Ecdsa-Signature-Id": "<signature-id-placeholder>"
}
const rawBody = '{"id": "<event-id>", "data": "<event-data>"}'

// Obtainable from our public endpoint
const publicKey = "<public-key-placeholder>"

// 1. Get the signature from the new header
const hubEcdsaSignatureValue = requestHeaders["X-Hub-Ecdsa-Signature"]

// 2. Decode data for the Body, Public Key and Signature (using Buffers)
const rawBodyBuffer = Buffer.from(rawBody)
const publicKeyBuffer = Buffer.from(publicKey, 'base64')
const signatureBuffer = Buffer.from(hubEcdsaSignatureValue, 'hex')

// 3. Verify the Signature using sha512 as the algorithm
const isSignatureValid = crypto.verify("sha512", rawBodyBuffer, publicKeyBuffer, signatureBuffer)
console.log(isSignatureValid) // true or false
# frozen_string_literal: true

require 'base64'
require 'base16'
require 'openssl'

# Request data (REPRESENTATION PURPOSES ONLY)
request_headers = {
  'X-Hub-Ecdsa-Signature' => '<signature-key-placeholder>',
  'X-Hub-Ecdsa-Signature-Id' => '<signature-id-placeholder>',
}
raw_body = '{"id": "<event-id>", "data": "<event-data>"}'

# Obtainable from our public endpoint
public_key = '<public-key-placeholder>'

# 1. Get the signature from the new header
signature = request_headers['X-Hub-Ecdsa-Signature']

# 2. Decode data
decoded_pk = Base64.decode64(public_key)
decoded_signature = Base16.decode16(signature)

# 3. Verify the Signature using sha512 as the algorithm
key = OpenSSL::PKey::EC.new(decoded_pk)
is_signature_valid = key.verify(OpenSSL::Digest::SHA512.new, decoded_signature, raw_body)

puts is_signature_valid # true or false
using System;
using System.Text;
using System.Collections.Generic;
using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.Security;

namespace Sample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Request data (REPRESENTATION PURPOSES ONLY)
            var requestHeaders = new Dictionary<string, string>();
            requestHeaders.Add("X-Hub-Ecdsa-Signature", "<signature-key-placeholder>");
            requestHeaders.Add("X-Hub-Ecdsa-Signature-Id", "<signature-id-placeholder>");
            var rawBody = "{\"id\": \"<event-id>\", \"data\": \"<event-data>\"}";
            
            // Obtainable from our public endpoint
            var publicKey = "<public-key-placeholder>";
            
            // 1. Get the signture from the new header
            var hubEcdsaSignatureValue = requestHeaders["X-Hub-Ecdsa-Signature"];
            
            // 2. Decode data for the Body, Public Key and Signature (using Buffers)
            var rawBodyBytes = Encoding.ASCII.GetBytes(rawBody);
            var publicKeyBytes = Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String(publicKey)).Replace("\n","").Replace("-----BEGIN PUBLIC KEY-----", "").Replace("-----END PUBLIC KEY-----", ""));     
            var signatureBytes = Hex.Decode(hubEcdsaSignatureValue);
            
            // 3. Verify the Signature using sha512 as the algorithm
            var signer = SignerUtilities.GetSigner("SHA512withECDSA");
            
            signer.Init(false, PublicKeyFactory.CreateKey(publicKeyBytes));
            signer.BlockUpdate(rawBodyBytes, 0, rawBodyBytes.Length);
            
            var isSignatureValid = signer.VerifySignature(signatureBytes);
            Console.WriteLine(isSignatureValid); // true or false
        }
    }
}
import java.math.BigInteger;
import java.util.Base64;
import java.security.PublicKey;
import java.security.Signature;
import java.security.KeyFactory;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

public class Main{

  public static void main(String []args) throws Exception{
    // Request data (REPRESENTATION PURPOSES ONLY)
    Map<String,String> requestHeaders = new HashMap<>();
    requestHeaders.put("X-Hub-Ecdsa-Signature", "<signature-key-placeholder>");
    requestHeaders.put("X-Hub-Ecdsa-Signature-Id", "<signature-id-placeholder>");

    String rawBody = "{\"id\": \"<event-id>\", \"data\": \"<event-data>\"}";

    // Obtainable from our public endpoint
    String publicKey = "<public-key-placeholder>";

    // 1. Get the signature from the new header
    String signature = requestHeaders.get("X-Hub-Ecdsa-Signature");

    // 2. Decode data
    byte[] decodedSignature = new BigInteger(signature, 16).toByteArray();

    String cleanPKString = new String(Base64.getDecoder().decode(publicKey.getBytes("utf-8"))).replace("\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
    byte[] decodedPK = Base64.getDecoder().decode(cleanPKString.getBytes("utf-8"));

    // 3. Verify the Signature using sha512 as the algorithm
    KeyFactory kf = KeyFactory.getInstance("EC");
    PublicKey pubKey = kf.generatePublic(new X509EncodedKeySpec(decodedPK));

    Signature s = Signature.getInstance("SHA512withECDSA");
    s.initVerify( pubKey );
    s.update( rawBody.getBytes("utf-8") );

    System.out.println(s.verify( decodedSignature )); // true or false
  }
}

Scope

events:write (getting details from received events).

📘

Supported Regions and Base URLs


Did this page help you?