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
ICredentials teams = GPAL
// one Send, both channels
IGPALNotify alerts = GPAL
alerts
//
alerts
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.
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
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
setx GPAL_TEAMS_WEBHOOK "https://
// in the workflow, a webhook straight from its address, for a quick test only
GPAL
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.
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
// a refusal arrives as an ERROR event, a dead address as an EXCEPTION; the workflow carries on
GPAL
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.
Showing off some plain text in these paragraphs eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Here you can find different accents and emphasis sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
This is a link and how it could look like bestlinkinthebeautifulworld. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Here's just some classic bold text adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam notBoldSecondbestlinkinthebeautifulworld illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at adipisicing elit libero vel voluptas? Expedita, adipisicing facere dolores voluptatem ad ab rem assumenda soluta!
Other cuple of colors in case we want to emphasize several ways adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at voluptatem libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repudiandae quas consequuntur illo numquam assumenda autem exercitationem distinctio perspiciatis in natus. Eius dicta similique ipsam ipsa minima, nemo quae enim tempore.
GPAL
.CallIfNotFound(GenericCallIfNotFound)
.WithPublishToConsole();
//System.Drawing.Rectangle windowSize = new System.Drawing.Rectangle(10, 10, 1500, 1024);
// NOTE: we have to set browser = before we execute any steps
// this is due to the 'GenericCallIfNotFound' which might throw an exception, and BankScraper will not have the browser set when it calls scraper.Close()
// until the complete fluent line gets executed (meaning every step, meaning browser is not set until everything else succeeds)
browser = GPAL.Browser
.WithBrowserType(Enums.BrowserType.Chrome)
.WithProfileDataDirectory(ChromeProfileLocation)
.WithUseAutomationEngine(AutomationEngine.Selenium)
.WithWindowSize(new System.Drawing.Rectangle(0,0,1920,1080))
.ToGPALObject();