Post a message to Teams from Delphi using WebHooks

Want to send a message to a Teams channel from your Delphi Application?
It is easy – simply post the message to your Teams channel’s WebHook URL.

Update – added Uwe Raabe’s TRESTClient version.

Well, perhaps not these hooks.
Photo by cottonbro on Pexels.com

Here is the low down basics of how to do it from Delphi. You simply do a http post with a preformatted Json string.

unit O365WebHook;

// Lars Fosdal, 2020 OCT 16
// Simple example without error handling

interface
uses
  System.Classes, System.SysUtils, System.Json, Rest.Json,
  IDGlobal, IdHTTP, IdIOHandler, IdSSL, IdSSLOpenSSL;

type
  TWebHookMessage = class
  end;

/// <summary> See https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using
/// for examples of how to structure the json for creating advanced formats</summary>
  TSimpleText = class(TWebHookMessage)
  private
    FText: String;
  public
    property Text: String read FText write FText;
    constructor Create(const aText: string);
  end;

type
  TWebHook = class
  private
    FHTTP: TIdHTTP;
    FURL: string;
  protected
    property HTTP: TIdHTTP read FHTTP;
  public
    constructor Create(const aURL: string = '');
    destructor Destroy; override;

    procedure Post(const aJson: string);
    procedure PostMessage(const aMsg: TWebhookMessage);

    property URL:String read FURL write FURL;
  end;


implementation

{ TWebHook }

constructor TWebHook.Create(const aURL: string);
begin
  FURL := aURL;
  // Remember that you need libeay32.dll and ssleay32.dll in the path for https to work
  FHTTP := TIdHTTP.Create(nil);
  HTTP.Request.ContentType := 'application/json';
  HTTP.Request.ContentEncoding := 'utf-8';
  HTTP.Request.CacheControl := 'no-cache';
  HTTP.HTTPOptions := HTTP.HTTPOptions - [hoForceEncodeParams] + [hoNoProtocolErrorException, hoWantProtocolErrorContent];
end;

destructor TWebHook.Destroy;
begin
  FHTTP.Free;
  inherited;
end;

procedure TWebHook.Post(const aJson: string);
var
  ReqString: TStringList;
  ResStream: TStringStream;
begin
  ReqString := TStringList.Create;
  try
    try
      ReqString.Text := aJson;
      ResStream := TStringStream.Create;
      try
//        DebugOut('HTTP Post ' + ReqString.Text);
        HTTP.Post(URL, ReqString, ResStream, IndyTextEncoding_UTF8);
//        DebugOut('Response: ' + ResStream.DataString);
      finally
        ResStream.Free;
      end;
    except // HTTP Exception
      on E: Exception
      do begin
//        DebugOutException(E, 'HTTP Post');
      end;
    end;
  finally
    ReqString.Free;
  end;
end;

procedure TWebHook.PostMessage(const aMsg: TWebhookMessage);
begin
  try
    Post(
      TJson.ObjectToJsonString(
        aMsg,
        [joIgnoreEmptyStrings, joIgnoreEmptyArrays, joDateIsUTC, joDateFormatISO8601]
      )
    );
  finally
    aMsg.Free;
  end;
end;

{ TSimpleText }

constructor TSimpleText.Create(const aText: string);
begin
  Text := aText;
end;

end.

How to use it. This is a very minimal example for a single text line.

procedure TestExampleToATeamsChannel;
var
  Teams: TWebhook;
begin
  Teams := TWebhook.Create('https:// ... YourLongWebHookURLHere ... ');
  try
    Teams.PostMessage(TSimpleText.Create('Hello from Delphi! :"{/=''æ}[øå] ÆØÅ'));
  finally
    Teams.Free;
  end;
end;

Make sure to read up on throttling and other rules of engagement before you start spamming your Teams channels. See the MS article on WebHooks for more detail.

Update! Uwe Raabe did a refurbished version that eliminates Indy and the need for the the OpenSLL DLLs, and makes the code work cross platform!

unit O365WebHook;

// Lars Fosdal, 2020 OCT 16
// Simple example without error handling
// Uwe Raabe replaced Indy for TRESTClient, eliminating the need for the OpenSLL DLLS

interface
uses
  System.Classes, System.SysUtils,
  REST.Json, REST.Client, REST.Types;

type
  TWebHookMessage = class
  end;

/// <summary> See https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using
/// for examples of how to structure the json for creating advanced formats</summary>
  TSimpleText = class(TWebHookMessage)
  private
    FText: String;
  public
    property Text: String read FText write FText;
    constructor Create(const aText: string);
  end;

type
  TWebHook = class
  private
    FClient: TRESTClient;
    FRequest: TCustomRESTRequest;
    FURL: string;
  protected
    property Client: TRESTClient read FClient;
    property Request: TCustomRESTRequest read FRequest;
  public
    constructor Create(const aURL: string = '');
    destructor Destroy; override;
    function PostMessage(const aMsg: TWebhookMessage; aOwnsMsg: Boolean = False): Boolean;
    property URL: string read FURL write FURL;
  end;

implementation

{ TWebHook }

constructor TWebHook.Create(const aURL: string);
begin
  inherited Create;
  FURL := aURL;

  FClient := TRESTClient.Create(nil);
  FRequest := TCustomRESTRequest.Create(nil);
  FRequest.Client := FClient;
end;

destructor TWebHook.Destroy;
begin
  FRequest.Free;
  FClient.Free;
  inherited;
end;

function TWebHook.PostMessage(const aMsg: TWebhookMessage; aOwnsMsg: Boolean = False): Boolean;
begin
  try
    Request.Client.BaseURL := URL;
    Request.Method := rmPOST;
    Request.AddBody(aMsg);
    Request.Execute;
    Result := Request.Response.Status.Success;
  finally
    if aOwnsMsg then
      aMsg.Free;
  end;
end;

{ TSimpleText }

constructor TSimpleText.Create(const aText: string);
begin
  inherited Create;
  FText := aText;
end;

end.

There is a known issue with a workaround for Delphi 10.2.

Have fun!