Webex Teams API Client
is a Library that wraps Cisco Webex Teams REST API
.
Also, some useful features for developers are provided.
By using Webex Teams API Client
, you can invoke Cisco Webex Teams REST API
easily.
- A Simple example to post a message as markdown.
// Load encrypted bot token from storage.
ProtectedString token = LoadEncryptedBotToken();
// Create a TeamsAPIClient instance with retry option.
var teams = TeamsAPI.CreateVersion1Client(token, new TeamsRetryHandler(4));
// Build markdown.
var markdown = new MarkdownBuilder();
markdown.Append("Hi, ").AppendBold("Webex Teams").Append("!!");
// Post a message.
var message = (await teams.CreateDirectMessageAsync("your_webex_teams_account@example.com", markdown.ToString())).GetData();
Console.WriteLine("Message was posted: ID = {0}", message.Id);
- A Simple example to post a message with Adaptive Cards created from Json string or object.
// Create From Json String.
var card = AdaptiveCardAttachment.FromJsonString(
@"
{
""type"": ""AdaptiveCard"",
""version"": ""1.0"",
""body"": [
{
""type"": ""TextBlock"",
""text"": ""Adaptive Cards"",
""size"": ""large""
}
],
""actions"": [
{
""type"": ""Action.OpenUrl"",
""url"": ""http://adaptivecards.io"",
""title"": ""Learn More""
}
]
}
");
// Post a message with card.
var message = (await teams.CreateDirectMessageAsync("your_webex_teams_account@example.com",
"[Learn More](http://adaptivecards.io) about Adaptive Card.",
card)).GetData();
Console.WriteLine("Message was posted: ID = {0}", message.Id);
// Also, you can create from Anonymous type or class instance.
var cardObj = new
{
type = "AdaptiveCard",
version = "1.0",
body = new []
{
new
{
type = "TextBlock",
text = "Adaptive Cards",
size = "large",
}
},
actions = new []
{
new
{
type = "Action.OpenUrl",
url = "http://adaptivecards.io",
title = "Learn More"
}
},
};
card = AdaptiveCardAttachment.FromObject(cardObj);
// Post a message with card.
message = (await teams.CreateDirectMessageAsync("your_webex_teams_account@example.com",
"[Learn More](http://adaptivecards.io) about Adaptive Card.",
card)).GetData();
Console.WriteLine("Message was posted: ID = {0}", message.Id);
// You can use teams.GetAttachmentActionAsync() to get action from users.
// Webhook listner, event handler in this APIClient has capablities to get these actions.
- An example to use Guest Issuer
It is an example that facilitates Guest Issuer of Webex Teams.
// Load encrypted Guest Issuer secret from storage.
ProtectedString secret = LoadEncryptedGuestIssuerSecret();
// Create a GuestIssuerClient instance.
var guestIssuer = TeamsAPI.CreateVersion1GuestIssuerClient(secret, "your_guest_issuer_id");
// Create a Guest User.
var guest = (await guestIssuer.CreateGuestUserAsync("my-guest-id", "GuestUserName")).GetData();
// Create a TeamsAPIClient instance for the Guest User with retry option.
var teams = TeamsAPI.CreateVersion1Client(guest, new TeamsRetryHandler(4));
// Post a message from the Guest User.
var message = (await teams.CreateDirectMessageAsync("your_webex_teams_account@example.com", "Hello, I am a guest!!")).GetData();
Console.WriteLine("Message was posted: ID = {0}", message.Id);
- An example to facilitate Pagination
It is an example that facilitates Pagination feature of Webex Teams REST API.
It demonstrates to get and iterate for each 50 spaces, then say "Hello" to the specific named space.
// Gets Enumerator to list all the Group spaces.
var e = (await teams.ListSpacesAsync(
type: SpaceType.Group,
max: 50)
).GetListResultEnumerator();
// Iterates until getting all the spaces.
while (await e.MoveNextAsync())
{
var r = e.CurrentResult;
if (r.IsSuccessStatus && r.Data.HasItems)
{
// Each result has space list.
foreach (var space in r.Data.Items)
{
// Say "Hello" to the specific named space.
if (space.Title == "Demo space for Webex Teams API Client(Thrzn41.WebexTeams)")
{
await teams.CreateMessageAsync(space, "Hello, Webex Teams!!");
}
}
}
}
More samples are here.
- .NET Standard 1.3 or later
- .NET Core 1.0 or later
- .NET Framework 4.5.2 or later
NOTE: If you use Simple Webhook Listener/Server feature,
.NET Standard 2.0+, .NET Core 2.0+ or .NET Framework 4.5.2+ is required.
Samples for Webex Teams API Client is available on here.
- Basic Webex Teams APIs(List/Get/Create Message, Space, etc.).
- Webex Teams Admin APIs(List/Get Event, License, etc.).
- Encrypt/Decrypt Webex Teams token in storage.
- AdaptiveCards attachment(Create Cards and Get AttachmentActions).
- Pagination for list APIs. Enumerator to facilitate the pagination.
- Retry-after value, Retry handler.
- Markdown builder.
- Error code, error description.
- Webhook secret validator, Webhook notification manager, Webhook event handler.
- Guest Issuer helper.
- OAuth2 helper.
- Simple Webhook Listener/Server(.NET Standard 2.0+, .NET Core 2.0+, .NET Framework 4.5.2+).
Teams Resource | Available Feature | Description |
---|---|---|
Person/People | List/Get | Available in v1.2.2. Get Me is also available |
Space(Room) | List/Create/Get/Update/Delete | Available in v1.2.2. Room is called 'Space' in this API Client. |
Space(Room) Meeting Info | Get | Available in v1.7.1. |
SpaceMembership(Membership) | List/Create/Get/Update/Delete | Available in v1.2.2. Membership is called 'SpaceMembership' in this API Client. |
Message | List/Create/Get/Delete | Available in v1.2.2. Attach file from local stream is also available |
AdaptiveCards | List/Create/Get/Delete | Available in v1.7.1 |
AttachmentActions | Create/Get | Available in v1.7.1 |
Team | List/Create/Get/Update/Delete | Available in v1.2.2. |
TeamMembership | List/Create/Get/Update/Delete | Available in v1.2.2. |
Webhook | List/Create/Get/Update/Delete | Available in v1.2.2. |
File | GetInfo/GetData/Upload | Available in v1.2.2. |
Place/Device/xAPI | - | Planed in v1.8.1. |
Teams Resource | Available Feature | Description |
---|---|---|
Person/People | Create/Update/Delete | Available in v1.2.2. |
Event | List/Get | Available in v1.2.2. |
Organization | List/Get | Available in v1.2.2. |
License | List/Get | Available in v1.2.2. |
Role | List/Get | Available in v1.2.2. |
GroupResource | List/Get | Available in v1.2.2. |
GroupResourceMembership | List/Get/Update | Available in v1.2.2. |
ProtectedString
provides token encryption/decryption.
More details are described later.
Cisco Webex Teams API pagination is described on here.
result.HasNext
and result.ListNextAsync()
are available in the Webex Teams API Client.
Also, TeamsListResultEnumerator
is available.
More details are described later.
result.HasRetryAfter
and result.RetryAfter
are available in the Webex Teams API Client.
Also, TeamsRetryHandler
and TeamsRetryOnErrorHandler
are available.
More details are described later.
result.HttpStatusCode
is available in the Webex Teams API Client.
More details are described later.
There are cases when Cisco Webex Teams API returns error with error code and description.
result.Data.HasErrors
and result.Data.GetErrors()
are available in the Webex Teams API Client.
There are cases when Cisco Webex Teams API returns partial errors.
This is described on here.
Item.HasErrors
and Item.GetPartialErrors()
are available in the Webex Teams API Client.
The trackingId may be used on technical support of Cisco Webex Teams API side.
result.TrackingId
is available in the Webex Teams API Client.
More details are described later.
Webhook.CreateEventValidator()
is available in the Webex Teams API Client.
Also, WebhookNotificationManager
is available to facilicate event handling.
More details are described later.
CreateWebhookAsync() method in the Webex Teams API Client generates webhook secret
dynamically by default option.
MarkdownBuilder
is available in the Webex Teams API Client.
More details are described later.
TeamsOauth2Client
is available in the Webex Teams API Client.
Webhook listener feature provides simple Webhook server feature.
NOTE: This feature is intended to be used for quick testing purpose.
In production environment, more reliable server solution should be used.
WebhookListener
is available in the Webex Teams API Client.
More details are described later.
You can install Webex Teams API Client
from NuGet
package manager by any of the following methods.
-
NuGet Package Manager GUI
Search "Thrzn41.WebexTeams
" package and install. -
NuGet Package Manager CLI
PM> Install-Package Thrzn41.WebexTeams
- .NET Client
> dotnet add package Thrzn41.WebexTeams
If you want to use using directive, the following namespaces could be used.
using Thrzn41.Util
using Thrzn41.WebexTeams
using Thrzn41.WebexTeams.Version1
You can also use the following namespaces, if needed.
Thrzn41.WebexTeams.Version1.GuestIssuer
to use Guest Issuer helper.Thrzn41.WebexTeams.Version1.OAuth2
to use OAuth2 helper.Thrzn41.WebexTeams.Version1.Admin
to use Admin APIs.
A Webex Teams API Client instance should be re-used as long as possible.
/// Basic APIs.
TeamsAPIClient teams = TeamsAPI.CreateVersion1Client(token);
If you use Admin APIs, an instance for Admin APIs is required.
TeamsAdminAPIClient
has all the features of TeamsAPIClient
and Admin Features.
/// Admin APIs.
TeamsAdminAPIClient teams = TeamsAPI.CreateVersion1AdminClient(token);
NOTE: The 'token' is very sensitive information for Cisco Webex Teams API.
You MUST protect the 'token' carefully.
NEVER put it in source code directly or NEVER save it in unsecure manner.
Webex Teams API Client
provides some token encryption/decryption methods.
If you use your own token encryption/decryption/protection methods, you can create an instance with the decrypted token string.
char[] tokens = GetBotTokenFromBotOwner();
var protectedToken = LocalProtectedString.FromChars(tokens);
LocalProtectedString.ClearChars(tokens);
Save("token.dat", protectedToken.EncryptedData);
Save("entropy.dat", protectedToken.Entropy);
NOTE:
LocalProtectedString
does not provide in-memory protection.
This is intended to be used to save and load encrypted token.
byte[] encryptedData = Load("token.dat");
byte[] entropy = Load("entropy.dat");
var protectedToken = LocalProtectedString.FromEncryptedData(encryptedData, entropy);
/// Basic APIs.
TeamsAPIClient teams = TeamsAPI.CreateVersion1Client(protectedToken);
NOTE: The encrypted data can be decrypted only on the same local user or local machine as encrypted based on option parameter.
var result = await teams.CreateMessageAsync("xyz_space_id", "Hello, Teams!");
if(result.IsSuccessStatus)
{
Console.WriteLine("Message was posted: id = {0}", result.Data.Id);
}
You can use result.IsSuccessStatus
to check if the request is succeeded or not.
You can also use result.Data.HasErrors
and result.Data.GetErrorMessage()
to retrieve error code value of error description from Cisco Webex Teams API service.
var result = await teams.CreateMessageAsync("xyz_space_id", "Hello, Teams!");
if(result.IsSuccessStatus)
{
Console.WriteLine("Message was posted: id = {0}", result.Data.Id);
}
else
{
Console.WriteLine("Failed to post a message: status = {0}, trackingId = {1}", result.HttpStatusCode, result.TrackingId);
if(result.Data.HasErrors)
{
Console.WriteLine( result.Data.GetErrorMessage() );
}
}
If you preferred to catch Exception, you can use result.GetData()
to get data.
The result.GetData()
will throw TeamsResultException
on request error.
(On the other hand, result.Data
does not throw TeamsResultException
.)
try
{
var result = await teams.CreateMessageAsync("xyz_space_id", "Hello, Teams!");
var message = result.GetData();
Console.WriteLine("Message was posted: id = {0}", message.Id);
}
catch(TeamsResultException tre)
{
Console.WriteLine("Failed to post a message: status = {0}, trackingId = {1}, description = {2}",
tre.HttpStatusCode, tre.TrackingId, tre.Message);
}
using (var fs = new FileStream("path/myfile.png", FileMode.Open, FileAccess.Read, FileShare.Read))
using (var data = new TeamsFileData(fs, "imagefile.png", TeamsMediaType.ImagePNG))
{
var result = await teams.CreateMessageAsync("xyz_space_id", "Hello Teams with Attachment", data);
if(result.IsSuccessStatus)
{
Console.WriteLine("Message was posted with attachment: id = {0}", result.Data.Id);
}
}
var result = await teams.CreateDirectMessageAsync("targetuser@example.com", "Hello, Teams!");
if(result.IsSuccessStatus)
{
Console.WriteLine("Message was posted: id = {0}", result.Data.Id);
}
var result = await teams.ListSpacesAsync();
if(result.IsSuccessStatus && result.Data.HasItems)
{
foreach (var item in result.Data.Items) {
Console.WriteLine("Space: title = {0}", item.Title);
}
}
Get File info without downloading the file.
var result = await teams.GetFileInfoAsync(new Uri("https://api.example.com/path/to/file.png"));
if(result.IsSuccessStatus)
{
var file = result.Data;
Console.WriteLine("File: Name = {0}, Size = {1}, Type = {2}", file.Name, file.Size?.Value, file.MediaType?.Name);
}
Download the file.
In this example, the file data will be downloaded to memory stream.
using(var stream = new MemoryStream())
{
var result = await teams.CopyFileDataToStreamAsync(new Uri("https://api.example.com/path/to/file.png"), stream);
if(result.IsSuccessStatus)
{
var file = result.Data;
Console.WriteLine("File: Name = {0}, Size = {1}, Type = {2}", file.Name, file.Size?.Value, file.MediaType?.Name);
}
}
var result = await teams.ListSpacesAsync();
if(result.IsSuccessStatus)
{
//
// Do something...
//
if(result.HasNext)
{
// List next result.
result = await result.ListNextAsync();
if(result.IsSuccessStatus)
{
// ...
}
}
}
// Gets Enumerator to list all the Group spaces.
var e = (await teams.ListSpacesAsync(
type: SpaceType.Group,
max: 50)
).GetListResultEnumerator();
// Iterates until getting all the spaces.
while (await e.MoveNextAsync())
{
var r = e.CurrentResult;
if (r.IsSuccessStatus && r.Data.HasItems)
{
// Each result has space list.
foreach (var space in r.Data.Items)
{
Console.WriteLine("Title = {0}", space.Title);
}
}
}
var result = await teams.ListSpacesAsync();
Console.WriteLine("Status is {0}", result.HttpStatusCode);
var result = await teams.ListSpacesAsync();
if(result.IsSuccessStatus)
{
//
// Do something...
//
}
else if(result.HasRetryAfter)
{
Console.WriteLine("Let's retry: {0}", result.RetryAfter.Delta);
}
TeamsRetryHandler
and TeamsRetryOnErrorHandler
facilitate retry.
// Creates instance with Retry option(Max retry is 4 in this case).
var teams = TeamsAPI.CreateVersion1Client(token, new TeamsRetryHandler(4));
// If the request received HTTP 429. the request will be retried.
var result = await teams.GetMeAsync();
// If you want the client retry also on HTTP 500, 502, 503, 504 error response, use TeamsRetryOnErrorHandler.
// If there is no Retry-After header, the request will be retried after 15 sec delay in this case.
var teams = TeamsAPI.CreateVersion1Client(token, new TeamsRetryOnErrorHandler(4, TimeSpan.FromSeconds(15.0f)));
var result = await teams.ListSpacesAsync();
Console.WriteLine("Tracking id is {0}", result.TrackingId);
var md = new MarkdownBuilder();
// Creates markdown with mention and ordered list.
md.Append("Hi ").AppendMentionToPerson("xyz_person_id", "PersonName").AppendLine();
md.AppendOrderedList("Item1");
md.AppendOrderedList("Item2");
md.AppendOrderedList("Item3");
var result = await teams.CreateMessageAsync("xyz_space_id", md.ToString());
var webhook = await teams.GetWebhookAsync("xyz_webhook_id");
var validator = webhook.CreateEventValidator();
When an event is notified on webhook uri,
X-Spark-Signature will have hash code.
The validator can be used to validate the data.
byte[] webhookEventData = GetWebhookEventData();
if( validator.Validate(webhookEventData, "xyz_x_teams_signature_value") )
{
Console.WriteLine("Notified data is valid!");
}
Webhook notification manager manages webhooks and event notification.
- Create instance.
var notificationManager = new WebhookNotificationManager();
- Then, add webhook to manager with notification function.
var webhook = await teams.GetWebhookAsync("xyz_webhook_id");
notificationManager.AddNotification(
webhook,
(eventData) =>
{
Console.WriteLine("Event is notified, id = {0}", eventData.Id);
}
);
- On receiving webhook event.
byte[] webhookEventData = GetWebhookEventData();
// Signature will be checked and notified to function which is added earlier.
notificationManager.ValidateAndNotify(webhookEventData, "xyz_x_teams_signature_value", encodingOfData);
- Create webhook listener instance.
var listener = new WebhookListener();
- Add listening host and port.
The TLS/https connection SHOULD be used for listening port.
To do this, a valid certificate for the listener SHOULD be bound in your environment first by netsh
tools or related tools.
Add listening endpoint with the bound address and port.
var endpointUri = listener.AddListenerEndpoint("yourwebhookserver.example.com", 8443);
- Create webhook for the listener.
The endpointUri
returned by listener.AddListenerEndpoint()
is Uri of the webhook.
var result = await teams.CreateWebhookAsync(
"my webhook for test",
endpointUri,
EventResource.Message,
EventType.Created);
- Add webhook to listener with notification function.
var webhook = result.Data;
listener.AddNotification(
webhook,
async (eventData) =>
{
Console.WriteLine("Event is notified, id = {0}", eventData.Id);
if(eventData.Resource == EventResource.Message)
{
Console.WriteLine("Message, id = {0}", eventData.MessageData.Id);
}
}
);
- Start listener.
After starting listener, events will be notified to registered notification function.
listener.Start();
If you do not have global ip address.
You may be able to use tunneling services such as ngrok.
- Get ngrok and start it.
You can download ngrok command line tool from here.
The following command will start tunneling that will forward to port 8080 of localhost.
prompt> ngrok http 8080 --bind-tls=true
- Create webhook listener instance.
var listener = new WebhookListener();
- Add listening host and port.
The ngrok will forward to localhost
.
var endpointUri = listener.AddListenerEndpoint("localhost", 8080, false);
- Create webhook for the listener.
In this case, a tunneling service is used with ngrok,
The endpointUri
returned by listener.AddListenerEndpoint()
is Uri to be forwarded.
You should create webhook with ngrok uri.
If ngrok assigns https://ngrok-xyz.example.com
,
You need to create webhook with String.Format("https://ngrok-xyz.example.com{0}", endpointUri.AbsolutePath)
.
var result = await teams.CreateWebhookAsync(
"my webhook for test",
new Uri(String.Format("https://ngrok-xyz.example.com{0}", endpointUri.AbsolutePath)),
EventResource.Message,
EventType.Created);
- Add webhook to listener with notification function.
var webhook = result.Data;
listener.AddNotification(
webhook,
async (eventData) =>
{
Console.WriteLine("Event is notified, id = {0}", eventData.Id);
if(eventData.Resource == EventResource.Message)
{
Console.WriteLine("Message, id = {0}", eventData.MessageData.Id);
}
}
);
- Start listener.
After starting listener, events will be notified to registered notification function.
listener.Start();