Integrations

Telling a Channel with GPAL.Notify

One Line to a Channel

A webhook is an address a chat service hands out for one channel. Anything that posts to it shows up there, so there is no sign-in, no token to refresh and no session to keep alive.

WithWebhook takes the address and Send posts the message, shaped the way that service expects: Slack and most others get {"text": ...}, Teams gets an Adaptive Card, Discord gets {"content": ...}, ntfy gets plain text. Add several webhooks and one Send reaches all of them.

The notifier can be held and sent to again, which is what a long run wants: one message when it starts, one per stage, one at the end.

// the address is a secret, so it comes from the environment, not the source

ICredentials slack = GPAL.CredentialsFor(CredentialServiceType.StaticKey)

.WithKeyFromEnv("GPAL_SLACK_WEBHOOK")

.ToGPALObject();


ICredentials teams = GPAL.CredentialsFor(CredentialServiceType.StaticKey)

.WithKeyFromEnv("GPAL_TEAMS_WEBHOOK")

.ToGPALObject();


// one Send, both channels

IGPALNotify alerts = GPAL.Notify

.WithWebhook(slack)

.WithWebhook(teams)

.ToGPALObject();


alerts.Send("Nightly export started");

// ... the work

alerts.Send("Nightly export finished: [412] rows");

NOTE

Anyone holding a webhook's address can post to that channel. Keep it out of a workflow's source: WithKeyFromEnv reads it from an environment variable, and WithServiceKey or WithKeyFromApi work too. WithWebhook also takes the address as a plain string, which is fine for a quick test and nothing else. GPAL never writes the address to a log. Events name the service instead, and when a service answers with an error that repeats the address, the address is taken out before the error is published.

Getting a Webhook

Each service hands one out in a minute or two, once.

Slack: at api.slack.com/apps create an app from scratch, turn on Incoming Webhooks, and Add New Webhook to Workspace for the channel. The address starts https://hooks.slack.com/services/.

Teams: in the channel, choose Workflows, then Send webhook alerts to a channel, and pick the team and channel. The address is shown when the workflow is added, and again in the workflow's first step, When a Teams webhook request is received.

Discord: Edit Channel, Integrations, Webhooks, New Webhook. Telegram: a bot from BotFather, and the address is https://api.telegram.org/bot/sendMessage?chat_id=.

Set each as an environment variable once, with setx in a command prompt, and restart the program so it sees it.

// in a command prompt, once

setx GPAL_SLACK_WEBHOOK "https://hooks.slack.com/services/..."

setx GPAL_TEAMS_WEBHOOK "https://...the address the workflow showed..."


// in the workflow, a webhook straight from its address, for a quick test only

GPAL.Notify

.WithWebhook("https://hooks.slack.com/services/...")

.Send("testing");

WARNING

A webhook made in Slack's Workflow Builder rather than in an app has an address starting https://hooks.slack.com/triggers/, and it accepts only the fields the workflow declares. GPAL sends one field called text, so declare a text variable named exactly text and put it in the message, or use an app's incoming webhook, which takes text as it is.

When a Message Does Not Get Through

Send never stops a workflow. A service that refuses the message is published as an error carrying what the service said, such as Slack's invalid_payload or Telegram's chat not found, which usually names the problem. An address with nothing answering is published as an exception, and one that does not answer at all is given up on after 30 seconds. Either way the rest of the webhooks are still sent to.

An address that is not http or https is refused when it is added, so a missing environment variable shows up at WithWebhook rather than at Send.

Message sent means the service accepted it. For Slack, Discord and most others that means it is in the channel. A Teams workflow accepts the message first and posts it after, so a workflow that is switched off can accept a message it never shows.

GPAL.WithInformationHandler(OnGPALEvent)

.WithExceptionHandler(OnGPALEvent);


// a refusal arrives as an ERROR event, a dead address as an EXCEPTION; the workflow carries on

GPAL.Notify

.WithWebhook(slack)

.WithWebhook(teams)

.Send("Nightly export finished: [412] rows");

NOTE

Notify is for telling a channel your team reads. Posting to X, LinkedIn, Facebook or Instagram is publishing, with its own sign-in, app review and limits, and Notify does not do it.

💬 Ask GPAL