[{"data":1,"prerenderedAt":652},["ShallowReactive",2],{"bootstrap":3,"blog-how-we-built-the-commerce-layer-slackbot-with-node-js-and-slack-api":277},{"navigation":4,"globalConfig":196,"forms":218},{"headerSections":5,"headerFooterLinks":67,"footer":76,"legalLinks":153,"socialLinks":176},[6,39,44,50,57,62],{"hasSubMenu":7,"title":8,"links":9,"columns":38},true,"Product",[10,20,26,32],{"showTag":11,"_modelApiKey":12,"text":13,"subtitle":14,"icon":15,"isExternal":11,"link":16,"openForm":11},false,"navigation_link","Universal checkout","Sell anywhere","globe-simple",{"_modelApiKey":17,"slug":18,"title":19},"page","universal-checkout","Universal Checkout",{"showTag":11,"_modelApiKey":12,"text":21,"subtitle":22,"icon":23,"isExternal":11,"link":24,"openForm":11},"Distributed OMS","Manage orders","package",{"_modelApiKey":17,"slug":25,"title":21},"distributed-oms",{"showTag":11,"_modelApiKey":12,"text":27,"subtitle":28,"icon":29,"isExternal":11,"link":30,"openForm":11},"Promotion engine","Automated discounts","seal-percent",{"_modelApiKey":17,"slug":31,"title":27},"promotion-engine",{"showTag":11,"_modelApiKey":12,"text":33,"subtitle":34,"icon":35,"isExternal":11,"link":36,"openForm":11},"POS system","Operate stores","devices",{"_modelApiKey":17,"slug":37,"title":33},"pos-system",2,{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":40,"isExternal":11,"link":41,"openForm":11,"title":40},"Pricing",{"_modelApiKey":42,"slug":43},"pricing_page","pricing",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":45,"isExternal":11,"link":46,"openForm":11,"title":45},"Customers",{"_modelApiKey":47,"slug":48,"title":49},"customer_landing_page","customers","Customers page",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":51,"isExternal":11,"link":52,"openForm":11,"form":54,"title":51},"Partners",{"_modelApiKey":17,"slug":53,"title":51},"partners",{"_modelApiKey":55,"slug":53,"name":56},"form","PartnersForm",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":58,"label":59,"isExternal":7,"url":60,"openForm":11,"title":61},"Docs Hub","Learn more","https:\u002F\u002Fdocs.commercelayer.io\u002F","Docs",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":63,"isExternal":11,"link":64,"openForm":11,"title":63},"Blog",{"_modelApiKey":65,"slug":66},"blog_landing_page","blog",[68,72],{"text":69,"link":70},"Sign in",{"_modelApiKey":12,"text":69,"isExternal":7,"url":71,"openForm":11},"https:\u002F\u002Fdashboard.commercelayer.io\u002Fsign_in",{"text":73,"link":74},"Sign up for free",{"_modelApiKey":12,"text":73,"isExternal":7,"url":75,"openForm":11},"https:\u002F\u002Fdashboard.commercelayer.io\u002Fsign_up",[77,89,109,132],{"hasSubMenu":7,"title":8,"links":78},[79,81,83,85,87],{"showTag":11,"_modelApiKey":12,"text":13,"subtitle":14,"icon":15,"isExternal":11,"link":80,"openForm":11},{"_modelApiKey":17,"slug":18,"title":19},{"showTag":11,"_modelApiKey":12,"text":21,"subtitle":22,"icon":23,"isExternal":11,"link":82,"openForm":11},{"_modelApiKey":17,"slug":25,"title":21},{"showTag":11,"_modelApiKey":12,"text":27,"subtitle":28,"icon":29,"isExternal":11,"link":84,"openForm":11},{"_modelApiKey":17,"slug":31,"title":27},{"showTag":11,"_modelApiKey":12,"text":33,"subtitle":34,"icon":35,"isExternal":11,"link":86,"openForm":11},{"_modelApiKey":17,"slug":37,"title":33},{"showTag":11,"_modelApiKey":12,"text":40,"isExternal":11,"link":88,"openForm":11},{"_modelApiKey":42,"slug":43},{"hasSubMenu":7,"title":90,"links":91},"Resources",[92,94,99,104,107],{"showTag":11,"_modelApiKey":12,"text":93,"isExternal":7,"url":60,"openForm":11},"Documentation",{"showTag":11,"_modelApiKey":12,"text":95,"label":95,"isExternal":11,"link":96,"openForm":11},"Agentic Commerce",{"_modelApiKey":17,"slug":97,"title":98},"agentic-commerce-whitepaper","Agentic Commerce Whitepaper",{"showTag":11,"_modelApiKey":12,"text":100,"isExternal":11,"link":101,"openForm":11},"Core concepts",{"_modelApiKey":17,"slug":102,"title":103},"headless-commerce","Headless commerce",{"showTag":11,"_modelApiKey":12,"text":105,"isExternal":7,"url":106,"openForm":11},"Changelog","https:\u002F\u002Fdocs.commercelayer.io\u002Fchangelog\u002F",{"showTag":11,"_modelApiKey":12,"text":63,"isExternal":11,"link":108,"openForm":11},{"_modelApiKey":65,"slug":66},{"hasSubMenu":7,"title":110,"links":111},"Company",[112,116,118,121,126],{"showTag":11,"_modelApiKey":12,"text":113,"isExternal":11,"link":114,"openForm":11},"Vision",{"_modelApiKey":17,"slug":115,"title":113},"vision",{"showTag":11,"_modelApiKey":12,"text":45,"isExternal":11,"link":117,"openForm":11},{"_modelApiKey":47,"slug":48,"title":49},{"showTag":11,"_modelApiKey":12,"text":51,"isExternal":11,"link":119,"openForm":11,"form":120},{"_modelApiKey":17,"slug":53,"title":51},{"_modelApiKey":55,"slug":53,"name":56},{"showTag":11,"_modelApiKey":12,"text":122,"isExternal":11,"link":123,"openForm":11},"Security",{"_modelApiKey":17,"slug":124,"title":125},"security","Security at Commerce Layer",{"showTag":11,"_modelApiKey":12,"text":127,"label":128,"isExternal":11,"openForm":7,"form":129},"Giveback","Giveback program",{"_modelApiKey":55,"slug":130,"name":131},"giveback","GivebackForm",{"hasSubMenu":7,"title":133,"links":134},"Get in touch",[135,140,145,149],{"showTag":11,"_modelApiKey":12,"text":136,"isExternal":11,"openForm":7,"form":137},"Contact",{"_modelApiKey":55,"slug":138,"name":139},"contact","ContactUsForm",{"showTag":11,"_modelApiKey":12,"text":141,"isExternal":11,"openForm":7,"form":142},"Book a demo",{"_modelApiKey":55,"slug":143,"name":144},"demo","RequestDemoForm",{"showTag":11,"_modelApiKey":12,"text":146,"label":147,"isExternal":7,"url":148,"openForm":11},"Support","Email support","mailto:support@commercelayer.io",{"showTag":11,"_modelApiKey":12,"text":150,"label":151,"isExternal":7,"url":152,"openForm":11},"Community","Discord community","https:\u002F\u002Fdiscord.gg\u002Fcommercelayer",{"hasSubMenu":7,"title":154,"links":155},"Legal",[156,161,165,169,173],{"showTag":11,"_modelApiKey":12,"text":157,"label":158,"isExternal":7,"url":159,"openForm":11,"htmlClassNames":160},"Terms","Terms and conditions","\u002Flegal-embed\u002Fterms-of-service","iubenda-nostyle no-brand iubenda-noiframe iubenda-embed",{"showTag":11,"_modelApiKey":12,"text":162,"label":163,"isExternal":7,"url":164,"openForm":11,"htmlClassNames":160},"Privacy","Privacy policy","\u002Flegal-embed\u002Fprivacy-policy",{"showTag":11,"_modelApiKey":12,"text":166,"label":167,"isExternal":7,"url":168,"openForm":11,"htmlClassNames":160},"Cookies","Cookie policy","\u002Flegal-embed\u002Fcookie-policy",{"showTag":11,"_modelApiKey":12,"text":170,"isExternal":7,"url":171,"openForm":11,"htmlClassNames":172},"Notice at collection","#","iubenda-cs-uspr-link",{"showTag":11,"_modelApiKey":12,"text":174,"isExternal":7,"url":171,"openForm":11,"htmlClassNames":175},"Privacy preferences","iubenda-cs-preferences-link",[177,180,184,188,192],{"title":178,"type":179,"url":152,"isExternal":7},"Discord","discord-logo",{"title":181,"type":182,"url":183,"isExternal":7},"Github","github","https:\u002F\u002Fgithub.com\u002Fcommercelayer",{"title":185,"type":186,"url":187,"isExternal":7},"Bluesky","bluesky-logo","https:\u002F\u002Fbsky.app\u002Fprofile\u002Fcommercelayer.io",{"title":189,"type":190,"url":191,"isExternal":7},"X","x-logo","https:\u002F\u002Fx.com\u002Fcommercelayer",{"title":193,"type":194,"url":195,"isExternal":7},"LinkedIn","linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fcommerce-layer",{"pagesWithoutCtaPanel":197,"ctaPanel":198,"notification":208,"showApiResponseTime":11,"newsletterSignup":216},[],{"title":199,"subtitle":200,"ctas":201},"Ready to get started?","Discover how we can help your business.",[202,205],{"_modelApiKey":203,"text":204,"isExternal":7,"url":75,"openForm":11},"link","Get started for free",{"_modelApiKey":203,"text":206,"isExternal":11,"openForm":7,"form":207},"Contact us",{"_modelApiKey":55,"slug":138,"name":139},{"showNotification":7,"link":209,"linkText":210,"text":215},{"_modelApiKey":203,"text":210,"isExternal":11,"link":211,"openForm":11},"Read article",{"_modelApiKey":212,"slug":213,"title":214},"blog_post","payment-api-redesigned","Introducing our new Payments API. For humans and agents.","\u003Cp>Introducing our new Payments API. For humans and agents.\u003C\u002Fp>\n",{"ctaText":217},"Subscribe",[219,225,229,236,240,246,249,253,261,262,267,272,274],{"_modelApiKey":55,"slug":220,"pardotFormEmbedCode":221,"formEmbedMinHeight":222,"successTitle":223,"successCopy":224},"agentic-commerce","https:\u002F\u002Fcommercelayer.fillout.com\u002Ft\u002F5w4Khwgxr6us?whitepaper=agentic",520,"Thank you for your request!","We’ll be in touch soon to arrange a date and time for your personalized demo.",{"_modelApiKey":55,"slug":226,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"successTitle":223,"successCopy":224},"shopify","https:\u002F\u002Fform.fillout.com\u002Ft\u002FgVnLSmKZwRus",850,{"_modelApiKey":55,"slug":130,"pardotFormEmbedCode":230,"formEmbedMinHeight":231,"displayTitle":232,"copy":233,"successTitle":234,"successCopy":235},"https:\u002F\u002Fform.fillout.com\u002Ft\u002FmVDF8aAiofus",500,"We give back.","\u003Cp>Part of Commerce Layer&#39;s company ethos is to empathize and support one another: co-workers, customers, and community. We want to do our part to help nonprofit organizations and small businesses who may feel adversely affected by the current global economic climate, and as a result, need in-kind support to reach their customers.\u003C\u002Fp>\n\n\u003Cp>If this general description matches your organization’s needs, please tell us your story by completing the following form so we can consider helping your cause.\u003C\u002Fp>\n","Thank you!","We will contact you soon to learn more about your story.",{"_modelApiKey":55,"slug":237,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":238,"copy":239},"book-an-onboarding-session","Book an onboarding session","\u003Cp>Schedule some time and our devs will help you set up a Commerce Layer + Sanity store.\u003C\u002Fp>\n",{"_modelApiKey":55,"slug":241,"pardotFormEmbedCode":242,"formEmbedMinHeight":243,"displayTitle":244,"successTitle":234,"successCopy":245},"download-our-whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=ComposableCommerce",450,"Download our whitepaper.","Check your inbox. You should receive a download link soon.",{"_modelApiKey":55,"slug":247,"pardotFormEmbedCode":248,"formEmbedMinHeight":243,"displayTitle":244,"successTitle":234,"successCopy":245},"download-the-content-and-commerce-whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=ContentAndCommerce",{"_modelApiKey":55,"slug":250,"pardotFormEmbedCode":251,"formEmbedMinHeight":243,"displayTitle":244,"copy":252,"successTitle":234,"successCopy":245},"whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=Jamstack","\u003Cp>Commerce Layer is the perfect platform for building commerce on the Jamstack. Convert any design into pixel perfect HTML pages. Connect a best-of-breed headless CMS. Go serverless and forget the hassle of managing complex infrastructures.\u003C\u002Fp>\n",{"_modelApiKey":55,"slug":254,"pardotFormEmbedCode":255,"formEmbedMinHeight":256,"displayTitle":257,"copy":258,"successTitle":259,"successCopy":260},"contact-links","https:\u002F\u002Fform.fillout.com\u002Ft\u002FeWPyhHGHT7us",750,"Contact us.","\u003Cp>Tell us about your project. Our team will reach out to you for a personalized offer.\u003C\u002Fp>\n","Thanks for your message!","We’ll be in touch soon to discuss your project details.",{"_modelApiKey":55,"slug":138,"pardotFormEmbedCode":255,"formEmbedMinHeight":256,"displayTitle":257,"copy":258,"successTitle":259,"successCopy":260},{"_modelApiKey":55,"slug":263,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":264,"successTitle":265,"successCopy":266},"demo-embed","Want to see it in action? Request a demo.","Thanks for your request!","We’ll be in touch soon to arrange a date and time for your free live demo.",{"_modelApiKey":55,"slug":53,"pardotFormEmbedCode":268,"formEmbedMinHeight":228,"displayTitle":269,"copy":270,"successTitle":234,"successCopy":271},"https:\u002F\u002Fform.fillout.com\u002Ft\u002F9MTkDWMUSqus","Become our partner.","\u003Cp>Join us and get our guidance in helping your clients succeed. Our partners are our ambassadors. We are committed to your success as much as you are.\u003C\u002Fp>\n","We will contact you soon to discuss the details of our partnership.",{"_modelApiKey":55,"slug":273,"pardotFormEmbedCode":268,"formEmbedMinHeight":228,"displayTitle":269,"copy":270,"successTitle":234,"successCopy":271},"partners-embed",{"_modelApiKey":55,"slug":143,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":275,"copy":276,"successTitle":265,"successCopy":266},"Get a free live demo.","\u003Cp>Get a personalized demo and learn more about what Commerce Layer can do for your company.\u003C\u002Fp>\n",{"_modelApiKey":212,"link":278,"subtitle":283,"date":283,"author":284,"additionalAuthors":294,"icon":295,"abstract":296,"tag":297,"displayTitle":279,"cardRatio":300,"featured":11,"seoMetaTags":301,"layout":365,"blogPostCarousel":563,"_seoMetaTags":613,"authors":647},{"text":279,"isExternal":11,"url":280,"link":281},"How to build a Commerce Layer Slackbot with Node.js and Slack API.","",{"_modelApiKey":212,"slug":282},"how-we-built-the-commerce-layer-slackbot-with-node-js-and-slack-api","April 4, 2023",{"name":285,"picture":286},"Bolaji Ayodeji",{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":291,"customData":293},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1643036479-bolaji.jpg","Bolaji Ayodeji — Developer Advocate at Commerce Layer",1280,"jpg",{"x":292,"y":292},0.5,{},[],"slack-logo","Learn how to build a cool Slackbot and integrate it with Commerce Layer using our JavaScript SDK, Nodejs, and the Slack bolt library.",{"color":298,"name":299},"gray","Tutorials","portrait",[302,306,310,313,317,320,323,327,331,335,338,342,346,350,354,357,361],{"attributes":303,"tag":304,"content":305},null,"title","Build a Slackbot with Commerce Layer and Node.js.",{"attributes":307,"tag":309,"content":303},{"property":308,"content":305},"og:title","meta",{"attributes":311,"tag":309,"content":303},{"name":312,"content":305},"twitter:title",{"attributes":314,"tag":309,"content":303},{"name":315,"content":316},"description","Learn how to build a cool ecommerce Slackbot and integrate it with Commerce Layer using our JavaScript SDK, Node.js, Slack's Bolt library, and Slack API.",{"attributes":318,"tag":309,"content":303},{"property":319,"content":316},"og:description",{"attributes":321,"tag":309,"content":303},{"name":322,"content":316},"twitter:description",{"attributes":324,"tag":309,"content":303},{"property":325,"content":326},"og:image","https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1680129479-cl_slackbot-integration_1200x630_v2.jpg?auto=format&fit=max&w=1200",{"attributes":328,"tag":309,"content":303},{"property":329,"content":330},"og:image:width","1200",{"attributes":332,"tag":309,"content":303},{"property":333,"content":334},"og:image:height","630",{"attributes":336,"tag":309,"content":303},{"name":337,"content":326},"twitter:image",{"attributes":339,"tag":309,"content":303},{"property":340,"content":341},"og:locale","en",{"attributes":343,"tag":309,"content":303},{"property":344,"content":345},"og:type","article",{"attributes":347,"tag":309,"content":303},{"property":348,"content":349},"og:site_name","Commerce Layer",{"attributes":351,"tag":309,"content":303},{"property":352,"content":353},"article:modified_time","2025-10-08T16:21:45Z",{"attributes":355,"tag":309,"content":303},{"property":356,"content":280},"article:publisher",{"attributes":358,"tag":309,"content":303},{"name":359,"content":360},"twitter:card","summary_large_image",{"attributes":362,"tag":309,"content":303},{"name":363,"content":364},"twitter:site","@commercelayer",[366,372,383,386,394,397,405,408,413,416,418,421,424,427,430,433,436,439,441,444,447,450,458,461,469,472,475,477,480,483,486,488,491,493,496,499,501,504,506,509,511,514,536,539,547,550,552,555],{"_modelApiKey":367,"copy":368,"listStyle":369,"darkTheme":11,"buttons":370,"color":371,"indent":7},"wysiwyg","\u003Cp>A bot is a software application that runs automated tasks over the internet and can interact with users. Over time, bots have been used on different communication\u002Fsocial mediums (Discord, Slack, Twitter, etc.) to facilitate collaboration, moderate communities, enhance work, automate workflows, and lots more. A popular example in technology companies is Slack bots, which operate within the Slack messaging platform as Slack apps and can provide tons of automation workflows for users in a workspace. Slackbots can be programmed to perform various tasks, from simple commands to complex workflows.\u003C\u002Fp>\n\n\u003Cp>We built a Slackbot to show the flexibility of the Commerce Layer API and developer tools. The bot uses our \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fmfe-checkout\">hosted-checkout\u003C\u002Fa> library to provide order and return summaries and allow users to checkout pending orders directly from a Slack channel—reinforcing our idea of &quot;checkout anywhere.&quot; The possibilities of checkout are limitless, and you can do anything you want with our API and hosted-checkout, as you will see shortly. In this tutorial, we will show you how we built the Slackbot integration using TypeScript, Nodejs, Slack&#39;s Bolt JavaScript library, and Slack API. Hopefully, you can use this tutorial as a guide to learn how to build your own Slackbot or how to install\u002Fuse or set up a custom server for the Commerce Layer Bot.\u003C\u002Fp>\n\n\u003Chr>\n\n\u003Ch3>Prerequisites\u003C\u002Fh3>\n\n\u003Cul>\n\u003Cli>A general understanding of how APIs and the command-line work.\u003C\u002Fli>\n\u003Cli>Some prior knowledge of the JavaScript programming language and Nodejs.\u003C\u002Fli>\n\u003Cli>You have \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fnodejs.org\u002Fen\u002Fdownload\u002Fpackage-manager\">Nodejs\u003C\u002Fa>, \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fnpmjs.com\u002Fpackage\u002Fnodemon\">Nodemon\u003C\u002Fa>, \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Ftypescriptlang.org\u002Fdownload\">TypeScript\u003C\u002Fa>, and \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fngrok.com\u002Fdownload\">Ngrok\u003C\u002Fa> installed\u002Fcorrectly configured on your machine (kindly click on the link on each tool to learn how to install them).\u003C\u002Fli>\n\u003Cli>You have a \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fslack.com\u002Fget-started#\u002Fcreatenew\">Slack account\u003C\u002Fa> already.\u003C\u002Fli>\n\u003Cli>You have a \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fdashboard.commercelayer.io\u002Fsign_up\">Commerce Layer account\u003C\u002Fa> already.\u003C\u002Fli>\n\u003Cli>You should have set up your organization and created the required commerce data resources for your market. You can follow the \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fdocs.commercelayer.io\u002Fdevelopers\u002Fwelcome\u002Fonboarding-tutorial\">onboarding tutorial\u003C\u002Fa> or \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fdocs.commercelayer.io\u002Fdevelopers\u002Fwelcome\u002Fmanual-configuration\">manual configuration guide\u003C\u002Fa> to achieve this.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Chr>\n\n\u003Ch3>Getting started\u003C\u002Fh3>\n\n\u003Cp>Creating a Slackbot that integrates with Commerce Layer can be useful in different ways. For example, if you want to provide support to a customer and you have the order ID, you can quickly get a summary right inside your team Slack workspace before considering heading to the Commerce Layer dashboard, or you could easily fetch a summary of the last placed order for some reason. You can also get sales progress reports of your store, amongst other amazing use cases 😃.\u003C\u002Fp>\n\n\u003Cp>Slack provides \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\">an API\u003C\u002Fa> that allows you to connect, simplify, and automate work with Slack apps. The API comes with tons of features and supporting tools. There are also the Slack SDKs (e.g., Slack Nodejs SDK) which have all been deprecated since May 2021 in favor of the new kid in the block—Bolt (which exists for \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fslackapi\u002Fbolt-js\">JavaScript\u003C\u002Fa>, \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fslackapi\u002Fbolt-python\">Python\u003C\u002Fa>, and \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fslackapi\u002Fjava-slack-sdk\">Java\u003C\u002Fa>). With this, you can still access all Slack APIs, Interactive Messages, OAuth, and Incoming Webhooks. We will talk more about this Bolt framework shortly.\u003C\u002Fp>\n\n\u003Cp>Slack offers three popular APIs (\u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fweb\">Web API\u003C\u002Fa>, \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fapis\u002Fconnections\u002Fevents-api\">Events API\u003C\u002Fa>, \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Frtm\">RTM API\u003C\u002Fa>) that allow you to build various apps with different requirements and use cases. In summary, the Web API is used to query information from and make changes to a Slack workspace, the Events API is used to build apps and bots that respond to activities in Slack, and the RTM (Real Time Messaging) API is used to receive events from Slack in real-time and send messages as users or bot users. If you’re confused about which to use for the app you have in mind, kindly refer to \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fmedium.com\u002Fslack-developer-blog\u002Fgetting-started-with-slacks-apis-f930c73fc889\">this official article\u003C\u002Fa> from the Slack platform blog. In our case, we want to build a bot that will basically listen for messages in channels, and we will respond to that event; hence the Events API is our go-to.\u003C\u002Fp>\n\n\u003Cp>The Events API utilizes event subscriptions (you subscribe to specific events) to send JSON payloads to your server when triggered (e.g. when a message or reaction is posted). Your server is responsible for receiving the payload and determining how to respond. Slack apps using this API will follow this event-driven sequence of actions:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>A user does something that triggers an event subscription (🧐 sending a message, clicking a button, using a slash command, etc.).\u003C\u002Fli>\n\u003Cli>Slack sends a JSON payload describing that event to your server (🚁📦).\u003C\u002Fli>\n\u003Cli>Your server acknowledges the receipt of the event (🧾).\u003C\u002Fli>\n\u003Cli>Your business logic determines the appropriate action for that event (👩🏼‍🍳).\u003C\u002Fli>\n\u003Cli>Your server carries out the determined action (🍔).\u003C\u002Fli>\n\u003Cli>Your server responds back to Slack (🚁🎁).\u003C\u002Fli>\n\u003C\u002Fol>\n\n\u003Cp>Before implementing the bot, we need to create a Slack application, configure the application, and fetch all the required credentials needed for development. In this section, we will highlight all the essential steps required to create and set up a new slack app from scratch.\u003C\u002Fp>\n","directional",[],"red",{"_modelApiKey":373,"border":7,"caption":280,"image":374,"size":382,"darkTheme":11,"rounded":7,"color":371},"image_block",{"url":375,"alt":376,"title":376,"width":377,"height":378,"format":379,"focalPoint":380,"customData":381},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1680499138-image.png","Slack app settings page",2000,1129,"png",{"x":292,"y":292},{},"standard",{"_modelApiKey":367,"copy":384,"listStyle":369,"darkTheme":11,"buttons":385,"color":371,"indent":11},"\u003Ch4>Setting up the Slack app\u003C\u002Fh4>\n\n\u003Cp>Kindly follow the steps below to create a new slack app:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>\u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fslack.com\u002Fhelp\u002Farticles\u002F206845317-Create-a-Slack-workspace\">Create a new slack workspace\u003C\u002Fa> or ensure you belong to one. If you’re building a bot, you should create a new test workspace so you have all the flexibility to test things without disturbing others in your organization&#39;s workspace.\u003C\u002Fli>\n\u003Cli>Create a slack app using the “From scratch” or “From app manifest” option, which decides how to configure your application’s scopes and settings.\n\n\u003Col>\n\u003Cli>With the “From scratch” option, you will use Slack’s configuration UI to manually add basic info, scopes, settings, and features to the app.\u003C\u002Fli>\n\u003Cli>With the “From app” option, you will use a \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Freference\u002Fmanifests\">manifest\u003C\u002Fa> file (JSON or YAML) to manually add basic info, scopes, settings, and features to the app. This is a new feature, and it comes in very handy, for example, when you need to change your Ngrok request URLs in development across the app when you restart the Ngrok tunnel.\u003C\u002Fli>\n\u003C\u002Fol>\u003C\u002Fli>\n\u003Cli>Enter a name for your app and select a workspace to create the app into (don’t forget to use a test workspace), review the app details, and create the app.\u003C\u002Fli>\n\u003Cli>Update the app&#39;s basic display information (name, short description, long description, icon, and background color) in the provided fields.\u003C\u002Fli>\n\u003C\u002Fol>\n",[],{"_modelApiKey":373,"border":11,"caption":280,"image":387,"size":382,"darkTheme":11,"rounded":7,"color":371},{"url":388,"alt":389,"title":389,"width":390,"height":391,"format":379,"focalPoint":392,"customData":393},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1680499434-screenshot-2023-03-17-at-05-37-08.png","Slack app display information settings",1420,1198,{"x":292,"y":292},{},{"_modelApiKey":367,"copy":395,"listStyle":369,"darkTheme":11,"buttons":396,"color":371,"indent":11},"\u003Ch4>Setting up Slack tokens\u003C\u002Fh4>\n\n\u003Cp>Slack apps use \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fdocs\u002Foauth\">OAuth\u003C\u002Fa> to manage access to Slack’s APIs. When an app is installed, you’ll need a token that the app can use to make calls to the API (similar to \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fdocs.commercelayer.io\u002Fcore\u002Fauthentication\">how it works\u003C\u002Fa> with Commerce Layer API 😉). Basically, there are three main \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fauthentication\u002Ftoken-types\">token types\u003C\u002Fa> available to a Slack app: user (\u003Ccode>xoxp\u003C\u002Fcode>), bot (\u003Ccode>xoxb\u003C\u002Fcode>), and app (\u003Ccode>xapp\u003C\u002Fcode>) tokens. Kindly follow the steps below to create the required slack scopes and tokens:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>Navigate to the ”OAuth &amp; Permissions” tab in the dashboard and create permission for the desired token type.\u003C\u002Fli>\n\u003Cli>Scroll to the ”Scopes &gt; Bot Token Scopes” section and click the ”Add an OAuth Scope” button.\u003C\u002Fli>\n\u003Cli>For this integration, we added the following scopes (\u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fscopes\">you can see all here\u003C\u002Fa>):\n\n\u003Cul>\n\u003Cli>\u003Ccode>commands\u003C\u002Fcode> (Add shortcuts and\u002For slash commands that people can use).\u003C\u002Fli>\n\u003Cli>\u003Ccode>incoming-webhook\u003C\u002Fcode> (Create one-way webhooks to post messages to a specific channel).\u003C\u002Fli>\n\u003Cli>\u003Ccode>channels:history\u003C\u002Fcode> (View messages and other content in public channels that your slack app has been added to).\u003C\u002Fli>\n\u003Cli>\u003Ccode>chat:write\u003C\u002Fcode> (Post messages in approved channels and conversations).\u003C\u002Fli>\n\u003Cli>\u003Ccode>chat:write.public\u003C\u002Fcode> (Send messages to channels @yourSlackApp isn&#39;t a member of).\u003C\u002Fli>\n\u003Cli>\u003Ccode>groups:history\u003C\u002Fcode> (View messages and other content in private channels that your slack app has been added to)\u003C\u002Fli>\n\u003Cli>\u003Ccode>im:history\u003C\u002Fcode> (View messages and other content in direct messages that your slack app has been added to).\u003C\u002Fli>\n\u003Cli>\u003Ccode>im:write\u003C\u002Fcode> (Start direct messages with people).\u003C\u002Fli>\n\u003Cli>\u003Ccode>mpim:history\u003C\u002Fcode> (View messages and other content in group direct messages that your slack app has been added to).\u003C\u002Fli>\n\u003Cli>\u003Ccode>mpim:write\u003C\u002Fcode> (Start group direct messages with people).\u003C\u002Fli>\n\u003Cli>\u003Ccode>users:read\u003C\u002Fcode> (View people in a workspace).\u003C\u002Fli>\n\u003C\u002Ful>\u003C\u002Fli>\n\u003Cli>Scroll back to the top of the page and click ”Install App to Workspace” to Install the app to your Slack workspace and generate the required tokens.\u003C\u002Fli>\n\u003Cli>Once you authorize the installation, you’ll be redirected to the ”OAuth &amp; Permissions” page and see a ”Bot User OAuth Access Token” section.\u003C\u002Fli>\n\u003C\u002Fol>\n",[],{"_modelApiKey":373,"border":11,"caption":280,"image":398,"size":382,"darkTheme":11,"rounded":7,"color":371},{"url":399,"alt":400,"title":400,"width":401,"height":402,"format":379,"focalPoint":403,"customData":404},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1680499907-screenshot-2023-03-17-at-06-15-17.png","Slack OAuth & Permissions settings page",2026,1258,{"x":292,"y":292},{},{"_modelApiKey":367,"copy":406,"listStyle":369,"darkTheme":11,"buttons":407,"color":371,"indent":11},"\u003Ch3>Setting up the project\u003C\u002Fh3>\n\n\u003Cp>Now that we have a Slack app created and tokens generated, we can start up our TypeScript Nodejs server. You can explore \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fexamples\u002Ftree\u002Fmain\u002Fsolutions\u002Fcommercelayer-slackbot\">the finished code\u003C\u002Fa> on GitHub to get all the required files (including linting configurations), but for now, let’s create the most important files with this structure:\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":410,"showLineNumbers":11,"highlightLines":280,"language":411,"size":412},"code_block","┌── src\n\t\t├── orders\n\t\t\t\t├── getOrders.ts\n\t\t├── returns\n\t\t\t\t├── getReturns.ts\n\t\t├── utils\n\t\t\t\t├── config.ts\n\t\t\t\t├── customError.ts\n\t\t\t\t├── getToken.ts\n\t\t\t\t├── parseDates.ts\n├── .env\n├── .app.ts\n├── package.json\n└── tsconfig.json","markup","Standard",{"_modelApiKey":367,"copy":414,"listStyle":369,"darkTheme":11,"buttons":415,"color":371,"indent":11},"\u003Cp>PS: You can generate a new package.json file using the \u003Ccode>npm init\u003C\u002Fcode> command.\u003C\u002Fp>\n\n\u003Cp>Next, copy and paste the bot token generated earlier and the signing secret from the “Basic Information &gt; App Credentials” tab in the dashboard (Slack signs incoming requests to your server using this secret) as environment variables in the \u003Ccode>.env\u003C\u002Fcode> file like so:\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":417,"showLineNumbers":11,"highlightLines":280,"language":411,"size":412},"# Slack API\nSLACK_BOT_TOKEN=xoxb-12345678-xyz\nSLACK_SIGNING_SECRET=xyz36dxyzzzzz",{"_modelApiKey":367,"copy":419,"listStyle":369,"darkTheme":11,"buttons":420,"color":371,"indent":11},"\u003Cp>Next, install the following required dependencies (Commerce Layer \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fcommercelayer-js-auth\">authentication\u003C\u002Fa> library, Slack’s \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fslackapi\u002Fbolt-js\">Bolt library\u003C\u002Fa> for JavaScript, and \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fmotdotla\u002Fdotenv\">Dotenv\u003C\u002Fa>) with the command:\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":422,"showLineNumbers":11,"highlightLines":280,"language":423,"size":412},"npm i @commercelayer\u002Fjs-auth @slack\u002Fbolt dotenv","shell",{"_modelApiKey":367,"copy":425,"listStyle":369,"darkTheme":11,"buttons":426,"color":371,"indent":11},"\u003Cp>Next, add the following code to the \u003Ccode>app.ts\u003C\u002Fcode> file:\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":428,"showLineNumbers":11,"highlightLines":280,"language":429,"size":412},"import * as dotenv from \"dotenv\";\ndotenv.config();\n\nimport { App, LogLevel } from \"@slack\u002Fbolt\";\n\n\u002F\u002F Initialize the app with your bot token and signing secret\nconst app = new App({\n\tlogLevel: LogLevel.DEBUG,\n  token: process.env.SLACK_BOT_TOKEN,\n  signingSecret: process.env.SLACK_SIGNING_SECRET\n});\n\n\u002F\u002F Start the app\n(async () => {\n  await app.start(process.env.PORT || 3000);\n\n  console.log('⚡️ Bolt app is running!');\n})();","javascript",{"_modelApiKey":367,"copy":431,"listStyle":369,"darkTheme":11,"buttons":432,"color":371,"indent":11},"\u003Cp>Next, add the following to your \u003Ccode>tsconfig.json\u003C\u002Fcode> file:\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":434,"showLineNumbers":11,"highlightLines":280,"language":435,"size":412},"{\n  \"compilerOptions\": {\n    \"target\": \"es2022\",\n    \"module\": \"node16\",\n    \"lib\": [\"es2022\"],\n    \"moduleResolution\": \"node16\",\n    \"rootDir\": \".\",\n    \"outDir\": \"build\",\n    \"allowSyntheticDefaultImports\": true,\n    \"importHelpers\": true,\n    \"alwaysStrict\": true,\n    \"sourceMap\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": false,\n    \"strictNullChecks\": false\n  }\n}","json",{"_modelApiKey":367,"copy":437,"listStyle":369,"darkTheme":11,"buttons":438,"color":371,"indent":11},"\u003Cp>Next, add the following scripts to your \u003Ccode>package.json\u003C\u002Fcode> file:\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":440,"showLineNumbers":11,"highlightLines":280,"language":435,"size":412},"{\n\t\"scripts\": {\n    \"dev\": \"ngrok http 3000\",\n    \"start\": \"nodemon build\u002Fapp.js\",\n    \"build\": \"npx tsc -w\",\n    \"test\": \"\"\n  },\n}",{"_modelApiKey":367,"copy":442,"listStyle":369,"darkTheme":11,"buttons":443,"color":371,"indent":11},"\u003Cp>Finally, run the command \u003Ccode>npm run build\u003C\u002Fcode> to automatically compile all TypeScript files into the \u003Ccode>\u002Fbuild\u003C\u002Fcode> folder with the same file structure. You can then start the Nodejs server using the command \u003Ccode>npm run start\u003C\u002Fcode>. Since we need our local server to receive a payload from Slack, we need to use a distributed reverse proxy web service to run the local server on the internet through an HTTP tunnel without having to deploy our code yet. Ngrok is a popular tool for this, and you should have installed it from the prerequisites section above. Now run \u003Ccode>npm run dev\u003C\u002Fcode> to start a Ngrok server on port \u003Ccode>3000\u003C\u002Fcode>; this will generate a URL (like \u003Ccode>https:\u002F\u002Ff09d-2a09-bac5.eu.ngrok.io\u003C\u002Fcode>) proxied to \u003Ccode>http:\u002F\u002Flocalhost:3000\u003C\u002Fcode>.\u003C\u002Fp>\n",[],{"_modelApiKey":445,"copy":446,"color":371},"highlight_block","For the sake of this tutorial, \"Slack bot\" and \"Slack app\" mean the same thing, and we will use them interchangeably. Slack calls all integrations using their API \"Slack apps.\" Not all apps are bots hence the different nomenclature. But a Slack bot is still a Slack app.",{"_modelApiKey":367,"copy":448,"listStyle":369,"darkTheme":11,"buttons":449,"color":371,"indent":11},"\u003Ch4>Setting up Slack events\u003C\u002Fh4>\n\n\u003Cp>Slack bots listen for events happening in a Slack workspace (like when a message is posted or when a reaction is posted to a message), and as mentioned earlier, we will use the Events API to subscribe to these events. There are two ways to do this, the default \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fapis\u002Fconnections\u002Fevents-api\">HTTP\u003C\u002Fa> or \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fapis\u002Fconnections\u002Fsocket\">Socket Mode\u003C\u002Fa>. Socket Mode lets apps use the Events API and interactive components without exposing a public HTTP endpoint, while HTTP Mode does the opposite. The HTTP method is recommended for apps being deployed to hosting environments or apps intended for distribution in the Slack App Directory, which is our end goal; hence we would use this method.\u003C\u002Fp>\n\n\u003Cp>When you use the \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fapis\u002Fconnections\u002Fevents-api\">Events API\u003C\u002Fa> directly over HTTP, you designate a public HTTP endpoint that your app listens on, choose what events to subscribe to, and Slack sends the appropriate events to you. Kindly follow the steps below to create events:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>Navigate to the ”Events &amp; Subscriptions” tab in the dashboard and toggle the “Enable Events” switch.\u003C\u002Fli>\n\u003Cli>Now enter the previously created Ngrok URL into the Request URL field, ending it with a \u003Ccode>\u002Fslack\u002Fevents\u003C\u002Fcode> suffix (i.e., \u003Ccode>https:\u002F\u002Ff09d-2a09-bac5.eu.ngrok.io\u002Fslack\u002Fevents\u003C\u002Fcode>).  Slack will then send HTTP POST requests based on triggered events to the provided Request URL endpoint.\u003C\u002Fli>\n\u003Cli>Next, configure the Slack events by selecting the specific events you want Slack to listen to. In the “Event Subscriptions” tab, scroll to the “Subscribe to bot events” section and click the “Add Bot User Event” button.\u003C\u002Fli>\n\u003Cli>For this integration, we added the following events:\n\n\u003Col>\n\u003Cli>\u003Ccode>message.channels\u003C\u002Fcode> (Listen for messages in public channels that your app is added to).\u003C\u002Fli>\n\u003Cli>\u003Ccode>message.groups\u003C\u002Fcode> (Listens for messages in private channels that your app is added to).\u003C\u002Fli>\n\u003Cli>\u003Ccode>message.im\u003C\u002Fcode> (Listen for messages in your app’s DMs with users).\u003C\u002Fli>\n\u003Cli>\u003Ccode>message.mpim\u003C\u002Fcode> (Listen for messages in multi-person DMs that your app is added to).\u003C\u002Fli>\n\u003Cli>\u003Ccode>app_home_opened\u003C\u002Fcode> (Listen for when a user clicks into your App Home)\u003C\u002Fli>\n\u003C\u002Fol>\u003C\u002Fli>\n\u003C\u002Fol>\n\n\u003Cp>The Slack bot is now ready to listen to messages from Slack. Let’s do one last thing and then write the business logic for the Slack bot. We’d show you how to acknowledge requests from Slack, process the requests, and respond back with a nicely formatted message.\u003C\u002Fp>\n\n\u003Ch4>Setting up Slack commands\u003C\u002Fh4>\n\n\u003Cp>In Slack, \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Finteractivity\u002Fslash-commands\">commands\u003C\u002Fa> enable users in a workspace to interact with Slack apps from within the Slack application (either on the web or with other clients). When a user enters a command and hits submit, this action will trigger an event and cause Slack to send a payload of data to the associated Slack app. The app will then respond in whatever way it wants based on the payload received. Kindly follow the steps below to create some commands:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>Navigate to the “Slash Commands” tab in the dashboard and click on the “Create New Command” button.\u003C\u002Fli>\n\u003Cli>Enter the command starting with a backslash character (e.g., \u003Ccode>\u002Fcl\u003C\u002Fcode>). Your command name must be shorter than 32 characters and must not contain spaces.\u003C\u002Fli>\n\u003Cli>Enter the Request URL we used previously (i.e., \u003Ccode>https:\u002F\u002Ff09d-2a09-bac5.eu.ngrok.io\u002Fslack\u002Fevents\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>Enter a short description of the command and some usage hints. These hints will form the parameters we will use later to write the business logic (e.g., if \u003Ccode>order\u003C\u002Fcode> comes after the \u003Ccode>\u002Fcl\u003C\u002Fcode> slash command, we should treat the next string after a space as the order ID).\u003C\u002Fli>\n\u003C\u002Fol>\n",[],{"_modelApiKey":373,"border":7,"caption":280,"image":451,"size":382,"darkTheme":11,"rounded":7,"color":371},{"url":452,"alt":453,"title":453,"width":454,"height":455,"format":379,"focalPoint":456,"customData":457},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1680535614-screenshot-2023-03-20-at-16-42-04.png","Slack slash commands settings page",2324,1304,{"x":292,"y":292},{},{"_modelApiKey":367,"copy":459,"listStyle":369,"darkTheme":11,"buttons":460,"color":371,"indent":11},"\u003Cp>To test this, quickly use the configured slash command (\u003Ccode>\u002Fcl test\u003C\u002Fcode>) in the workspace you have installed the Slack app into. Slack will send an HTTP POST to the Request URL with the following payload:\u003C\u002Fp>\n",[],{"_modelApiKey":373,"border":7,"caption":280,"image":462,"size":382,"darkTheme":11,"rounded":7,"color":371},{"url":463,"alt":464,"title":464,"width":465,"height":466,"format":379,"focalPoint":467,"customData":468},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1680535784-screenshot-2023-03-20-at-16-56-16.png","Ngrok events summary",1236,1160,{"x":292,"y":292},{},{"_modelApiKey":367,"copy":470,"listStyle":369,"darkTheme":11,"buttons":471,"color":371,"indent":11},"\u003Cp>You can view the requests yourself at \u003Ccode>http:\u002F\u002F127.0.0.1:4040\u002Finspect\u002Fhttp\u003C\u002Fcode> or see \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Finteractivity\u002Fslash-commands#app_command_handling\">more details\u003C\u002Fa> about the payload Slack sends in Slack’s documentation.\u003C\u002Fp>\n",[],{"_modelApiKey":367,"copy":473,"listStyle":369,"darkTheme":11,"buttons":474,"color":371,"indent":11},"\u003Ch4>Setting up interactivity\u003C\u002Fh4>\n\n\u003Cp>To use features like modals, shortcuts, or interactive components (like input fields, buttons, select menus, etc.), you will need to enable \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fmessaging\u002Finteractivity#components\">interactivity\u003C\u002Fa>. This allows your app respond back to Slack with a message when a request is triggered using the available \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Fblock-kit\u002Finteractivity\">interactive components\u003C\u002Fa>. And when a user interacts with these components (e.g., clicking a button), Slack will send the request as an event, and you can respond back the same way you handle slash commands. Kindly follow the steps below to configure interactivity:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>Navigate to the “Interactivity &amp; Shortcuts” tab in the dashboard and toggle the “Interactivity” switch.\u003C\u002Fli>\n\u003Cli>Enter the Request URL we used previously (i.e., \u003Ccode>https:\u002F\u002Ff09d-2a09-bac5.eu.ngrok.io\u002Fslack\u002Fevents\u003C\u002Fcode>) and hit save.\u003C\u002Fli>\n\u003Cli>All good!\u003C\u002Fli>\n\u003C\u002Fol>\n\n\u003Ch3>Bot features\u003C\u002Fh3>\n\n\u003Cp>The bot commands trigger certain requests, and the app returns some information about the requested resource (including the resource and customer details) and links to view the full details of certain resources in the Commerce Layer dashboard. Below you will find all the existing and future features of the Commerce Layer Slackbot integration as of when this tutorial was published.\u003C\u002Fp>\n\n\u003Cul>\n\u003Cli>☑️ Command to fetch the details of a particular order.\n\n\u003Cul>\n\u003Cli>\u003Ccode>\u002Fcl order [order ID]\u003C\u002Fcode> (fetch an order by ID)\u003C\u002Fli>\n\u003Cli>\u003Ccode>\u002Fcl orders:last\u003C\u002Fcode> and \u003Ccode>\u002Fcl orders:p last\u003C\u002Fcode> (fetch the last placed order)\u003C\u002Fli>\n\u003Cli>\u003Ccode>\u002Fcl orders:a last\u003C\u002Fcode> (fetch the last approved order)\u003C\u002Fli>\n\u003C\u002Ful>\u003C\u002Fli>\n\u003Cli>☑️ Command to fetch the details of a particular return.\n\n\u003Cul>\n\u003Cli>\u003Ccode>\u002Fcl return [return ID]\u003C\u002Fcode> (fetch a return by ID).\u003C\u002Fli>\n\u003Cli>\u003Ccode>\u002Fcl returns:last\u003C\u002Fcode> and \u003Ccode>\u002Fcl returns:r last\u003C\u002Fcode> (fetch the last placed return)\u003C\u002Fli>\n\u003Cli>\u003Ccode>\u002Fcl returns:a last\u003C\u002Fcode> (fetch the last approved return)\u003C\u002Fli>\n\u003C\u002Ful>\u003C\u002Fli>\n\u003Cli>☑️ Commands to fetch current total orders (count and revenue) per day.\n\n\u003Cul>\n\u003Cli>\u003Ccode>\u002Fcl orders:today &lt;currency code&gt;\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\u003C\u002Fli>\n\u003Cli>☑️ Commands to fetch current returns (count) per day.\n\n\u003Cul>\n\u003Cli>\u003Ccode>\u002Fcl returns:today\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\u003C\u002Fli>\n\u003Cli>☐ Automatic alerts for the total orders revenue at the end of the day\u002Fweek\u002Fmonth.\u003C\u002Fli>\n\u003Cli>☐ Automatic alerts for the total number of returns at the end of the day\u002Fweek\u002Fmonth.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Ch3>Fetching data from Commerce Layer SDK\u003C\u002Fh3>\n\n\u003Cp>We will now use the official Commerce Layer JavaScript library, which makes it quick and easy to interact with Commerce Layer API. If you don’t have any experience with using our SDK, you should read \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fcommercelayer.io\u002Fblog\u002Fgetting-started-with-commerce-layer-javascript-sdk\">this introductory article\u003C\u002Fa> from the blog. You can install the SDK using the command \u003Ccode>npm install @commercelayer\u002Fsdk\u003C\u002Fcode>. Kindly follow the steps below to get the SDK working on this project:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>Create an Integration application type on the Commerce Layer dashboard for your organization (you can learn how to do this from \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fdocs.commercelayer.io\u002Fcore\u002Fapplications#creating-an-application\">the documentation\u003C\u002Fa>). We will use this to generate an integration access token for the Slack bot.\u003C\u002Fli>\n\u003Cli>Create a Sales channel application type too. We will use this to generate a sales channel token for the \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fmfe-checkout\">hosted-checkout\u003C\u002Fa> library.\u003C\u002Fli>\n\u003Cli>Copy the generated API credentials (client ID, client secret, base endpoint, and allowed scopes).\u003C\u002Fli>\n\u003Cli>Add the following environmental variables into the \u003Ccode>.env\u003C\u002Fcode> file inputting the valid API credentials.\u003C\u002Fli>\n\u003C\u002Fol>\n",[],{"_modelApiKey":409,"code":476,"showLineNumbers":11,"highlightLines":280,"language":411,"size":412},"# Commerce Layer API (Integration)\nCL_ORGANIZATION_SLUG=cake-store\nCL_ORGANIZATION_MODE=test\nCL_CLIENT_ID=c0mMeRceLaYeRl0VEsyOUvLHxCmjklvyJQPYtU\nCL_CLIENT_ID_CHECKOUT=bizmY8bbCK2IoQfF-c0mMeRceLaYeRl0VEsyOU\nCL_CLIENT_SECRET=1DdUFqHuT212m8lRT4s7_Wv_aiwJd5s0qBcloj_jugds",{"_modelApiKey":367,"copy":478,"listStyle":369,"darkTheme":11,"buttons":479,"color":371,"indent":11},"\u003Cp>All requests to Commerce Layer API must be authenticated with a token. Hence, before using the SDK, you need to get a valid access token. Add the code below in \u003Ccode>src\u002Futils\u002Fconfig.ts\u003C\u002Fcode> to initialize the Commerce Layer SDK and generate an integration token for fetching resources from Commerce Layer API:\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":481,"showLineNumbers":11,"highlightLines":280,"language":429,"size":482},"import * as dotenv from \"dotenv\";\ndotenv.config();\n\nimport CommerceLayer from \"@commercelayer\u002Fsdk\";\nimport { getIntegrationToken } from \"@commercelayer\u002Fjs-auth\";\n\nexport const initConfig = async () => {\n  const BASE_ENDPOINT = `https:\u002F\u002F${process.env.CL_ORGANIZATION_SLUG}.commercelayer.io`;\n  const CLIENT_ID = process.env.CL_CLIENT_ID;\n  const CLIENT_ID_CHECKOUT = process.env.CL_CLIENT_ID_CHECKOUT;\n  const CLIENT_SECRET = process.env.CL_CLIENT_SECRET;\n  const organizationSlug = process.env.CL_ORGANIZATION_SLUG;\n  const organizationMode = process.env.CL_ORGANIZATION_MODE;\n\n  const { accessToken: token } = await getIntegrationToken({\n    endpoint: BASE_ENDPOINT,\n    clientId: CLIENT_ID,\n    clientSecret: CLIENT_SECRET\n  });\n\n  const cl = CommerceLayer({\n    organization: organizationSlug,\n    accessToken: token\n  });\n\n  return {\n    cl,\n    BASE_ENDPOINT,\n    CLIENT_ID,\n    CLIENT_ID_CHECKOUT,\n    CLIENT_SECRET,\n    organizationSlug,\n    organizationMode\n  };\n};","Wide",{"_modelApiKey":367,"copy":484,"listStyle":369,"darkTheme":11,"buttons":485,"color":371,"indent":11},"\u003Cp>Next add the code below in \u003Ccode>src\u002Futils\u002FgetToken.ts\u003C\u002Fcode> to generate a sales channel tokens for the hosted-checkout:\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":487,"showLineNumbers":11,"highlightLines":280,"language":429,"size":482},"import * as dotenv from \"dotenv\";\ndotenv.config();\n\nimport { getSalesChannelToken } from \"@commercelayer\u002Fjs-auth\";\n\nexport const getCheckoutToken = async (config, marketNumber: number) => {\n\tconst { BASE_ENDPOINT, CLIENT_ID, CLIENT_SECRET } = config;\n  const auth = await getSalesChannelToken({\n    endpoint: BASE_ENDPOINT,\n    clientId: CLIENT_ID_CHECKOUT,\n    scope: `market:${marketNumber}`\n  });\n  return auth.accessToken;\n};",{"_modelApiKey":367,"copy":489,"listStyle":369,"darkTheme":11,"buttons":490,"color":371,"indent":11},"\u003Cp>Now unto that main stuff! Add the code below to the \u003Ccode>src\u002Forders\u002FgetOrders.ts\u003C\u002Fcode> file, which includes three exported functions (\u003Ccode>getOrderById()\u003C\u002Fcode>, \u003Ccode>getLastOrder()\u003C\u002Fcode>, and \u003Ccode>getTodaysOrder()\u003C\u002Fcode>) that fetches some data from Commerce Layer and returns an object.\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":492,"showLineNumbers":11,"highlightLines":280,"language":429,"size":482},"import { getCheckoutToken } from \"..\u002Futils\u002FgetToken\";\nimport { generateDate } from \"..\u002Futils\u002FparseDate.js\";\n\nconst getOrderById = async (id: string, config) => {\n\tconst { cl, organizationSlug, organizationMode } = config;\n  try {\n    const orders = await cl.orders.retrieve(id, {\n      include: [\"customer\", \"market\", \"shipments\", \"shipping_address\", \"billing_address\", \"payment_method\"]\n    });\n\n    const checkoutAccessToken = await getCheckoutToken(config, orders.market.number);\n\n    return { orders, organizationSlug, organizationMode, checkoutAccessToken };\n  } catch (error) {\n    return { error };\n  }\n};\n\nconst getLastOrder = async (status: string, config) => {\n\tconst { cl, organizationSlug, organizationMode } = config;\n  try {\n    const orders = (\n      await cl.orders.list({\n        include: [\"customer\", \"market\", \"shipments\", \"shipping_address\", \"billing_address\", \"payment_method\"],\n        filters: { status_eq: `${status}` },\n        sort: status === \"placed\" ? { placed_at: \"desc\" } : { approved_at: \"desc\" }\n      })\n    ).first();\n\n    return { orders, organizationSlug, organizationMode };\n  } catch (error) {\n    return { error };\n  }\n};\n\nconst getTodaysOrder = async (currency: string, config) => {\n\tconst { cl, organizationSlug } = config;\n  const currencyName = currency.toUpperCase();\n\n  const allOrders = await cl.orders.list({\n    filters: {\n      status_eq: \"placed\",\n      placed_at_gteq: `${generateDate(\"today\")}}`,\n      placed_at_lt: `${generateDate(\"next\")}}`\n    }\n  });\n  const allOrdersCount = allOrders.meta.recordCount;\n\n  const allOrdersByMarket = await cl.orders.list({\n    filters: {\n      currency_code_eq: `${currencyName}`,\n      status_eq: \"placed\",\n      placed_at_gteq: `${generateDate(\"today\")}}`,\n      placed_at_lt: `${generateDate(\"next\")}}`\n    }\n  });\n  const allOrdersByMarketCount = allOrdersByMarket.meta.recordCount;\n\n  const revenue = allOrdersByMarket.reduce((acc, order) => {\n    return acc + order.total_amount_cents;\n  }, 0);\n\n  let revenueCount;\n  allOrdersByMarketCount !== 0\n    ? (revenueCount = (revenue \u002F 100).toLocaleString(\n        `${allOrdersByMarket[0].language_code}-${allOrdersByMarket[0].country_code}`,\n        {\n          style: \"currency\",\n          currency: `${allOrdersByMarket[0].currency_code}`\n        }\n      ))\n    : (revenueCount = 0);\n\n  return {\n    allOrdersCount,\n    allOrdersByMarketCount,\n    revenueCount,\n    currencyName,\n    organizationSlug\n  };\n};\n\nexport { getOrderById, getLastOrder, getTodaysOrder };",{"_modelApiKey":367,"copy":494,"listStyle":369,"darkTheme":11,"buttons":495,"color":371,"indent":11},"\u003Cp>You can find the code for the date formatter utility functions in \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fexamples\u002Ftree\u002Fmain\u002Fsolutions\u002Fcommercelayer-slackbot\u002Fsrc\u002Futils\u002FparseDate.ts\">this file\u003C\u002Fa> on GitHub.\u003C\u002Fp>\n",[],{"_modelApiKey":367,"copy":497,"listStyle":369,"darkTheme":11,"buttons":498,"color":371,"indent":11},"\u003Ch3>Using the Slack Bolt library\u003C\u002Fh3>\n\n\u003Cp>Now that we have everything set up, let’s write a few more code to acknowledge requests from Slack and respond back to them using the \u003Ccode>command()\u003C\u002Fcode> method. As described below, we will check if the submitted slash command starts with a particular parameter, then we will call another function from a different file that we will set up later and assign this function to either the \u003Ccode>getOrderResource\u003C\u002Fcode> or \u003Ccode>getReturnResource\u003C\u002Fcode> function. These modular functions will include the business logic that will receive some data objects and respond back to Slack. Kindly add the code below in addition to the existing \u003Ccode>app.ts\u003C\u002Fcode> file.\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":500,"showLineNumbers":11,"highlightLines":280,"language":429,"size":482},"import { initConfig } from \".\u002Fsrc\u002Futils\u002Fconfig\";\n\napp.command(\"\u002Fcl\", async ({ command, client, ack, say }) => {\n  await ack();\n\n\tconst config = await initConfig();\n\n  if (command.text.startsWith(\"order \")) {\n    const resourceType = getOrderById(command.text.replace(\"order \", \"\"), config);\n    getOrderResource(resourceType, command, client, say);\n  } else if (command.text === \"orders:p last\" || command.text === \"orders:last\") {\n    const resourceType = getLastOrder(\"placed\", config);\n    getOrderResource(resourceType, command, client, say);\n  } else if (command.text === \"orders:a last\") {\n    const resourceType = getLastOrder(\"approved\", config);\n    getOrderResource(resourceType, command, client, say);\n  }\n\n  if (command.text.startsWith(\"return \")) {\n    const resourceType = getReturnById(command.text.replace(\"return \", \"\"), config);\n    getReturnResource(resourceType, command, client, say);\n  } else if (command.text === \"returns:r last\" || command.text === \"returns:last\") {\n    const resourceType = getLastReturn(\"requested\", config);\n    getReturnResource(resourceType, command, client, say);\n  } else if (command.text === \"returns:a last\") {\n    const resourceType = getLastReturn(\"approved\", config);\n    getReturnResource(resourceType, command, client, say);\n  }\n\n  if (command.text.startsWith(\"orders:today \")) {\n    const resourceType = getTodaysOrder(command.text.replace(\"orders:today \", \"\"));\n    countOrders(resourceType, command, client, say);\n  }\n\n  if (command.text.startsWith(\"returns:today\")) {\n    const resourceType = getTodaysReturn();\n    countReturns(resourceType, command, client, say);\n  }\n});",{"_modelApiKey":367,"copy":502,"listStyle":369,"darkTheme":11,"buttons":503,"color":371,"indent":11},"\u003Cp>The code snippet below shows what the \u003Ccode>getOrderResource\u003C\u002Fcode> function looks like. The \u003Ccode>client.users.info()\u003C\u002Fcode> function accepts an object to fetch information about a user, and the \u003Ccode>say()\u003C\u002Fcode> function accepts a string to post simple messages or JSON payloads for more complex messages. Please read the comments in the code to better understand what each part does.\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":505,"showLineNumbers":11,"highlightLines":280,"language":429,"size":482},"const getOrderResource = async (resourceType, userInput, client, say) => {\n  \u002F\u002F Make a request to fetch the user information of the user that triggered the command.\n  \u002F\u002F We will use their profile image and display name in the response message.\n  const triggerUser = await client.users.info({\n    user: userInput.user_id\n  });\n  const resource = await resourceType;\n\n  \u002F\u002F If the functions in getOrders.ts returns undefined or an empty object, return an error message.\n  \u002F\u002F The custom error handler can be found here on GitHub here:\n  \u002F\u002F https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fexamples\u002Ftree\u002Fmain\u002Fsolutions\u002Fcommercelayer-slackbot\u002Fsrc\u002Futils\u002FcustomError.ts\n  if (!resource.orders) {\n    await say({\n      blocks: [\n        {\n          type: \"section\",\n          text: {\n            type: \"mrkdwn\",\n            text: `> :warning: Command ${\"`\"}${userInput.command} ${\n              userInput.text\n            }${\"`\"} failed with error: ${\"```\"}${JSON.stringify(customError(\"Order\"), null, 2)}${\"```\"}`\n          }\n        }\n      ],\n      \u002F\u002F Add a fallback plain text for notifications and accessibility.\n      text: customError(\"Order\")\n    });\n    \u002F\u002F If the functions in getOrders.ts returns some data object, return an interactive message.\n  } else {\n    await say({\n      blocks: [\n        {\n          type: \"section\",\n          text: {\n            type: \"mrkdwn\",\n            text: `:shopping_trolley: Order ${\"`\"}${resource.orders.id}${\"`\"} from the *${\n              resource.orders.market.name\n            }* market has a total amount of *${resource.orders.formatted_subtotal_amount}* and was ${\n              resource.orders.placed_at !== null ? \"placed\" : \"created\"\n            } on \u003C!date^${formatTimestamp(\n              resource.orders.placed_at !== null ? resource.orders.placed_at : resource.orders.created_at\n            )}^{date_long} at {time}|${\n              resource.orders.placed_at !== null ? resource.orders.placed_at : resource.orders.created_at\n            }>. Here's a quick summary of the resource:`\n          }\n        },\n        {\n          type: \"divider\"\n        },\n        {\n          type: \"section\",\n          fields: [\n            {\n              type: \"mrkdwn\",\n              text: `*Customer email:*\\n${resource.orders.customer !== null ? resource.orders.customer.email : \"null\"}`\n            },\n            {\n              type: \"mrkdwn\",\n              text: `*Customer ID:*\\n\u003Chttps:\u002F\u002Fdashboard.commercelayer.io\u002F${\n                resource.organizationMode === \"live\" ? \"live\" : \"test\"\n              }\u002F${resource.organizationSlug}\u002Fresources\u002Fcustomers\u002F${\n                resource.orders.customer !== null ? resource.orders.customer.id : \"null\"\n              }|${resource.orders.customer !== null ? resource.orders.customer.id : \"null\"}>`\n            }\n          ]\n        },\n        {\n          type: \"section\",\n          fields: [\n            {\n              type: \"mrkdwn\",\n              text: `*Order number:*\\n${\"`\"}${resource.orders.number}${\"`\"}`\n            },\n            {\n              type: \"mrkdwn\",\n              text: `*Order status:*\\n${\"`\"}${resource.orders.status}${\"`\"}`\n            }\n          ]\n        },\n        {\n          type: \"section\",\n          fields: [\n            {\n              type: \"mrkdwn\",\n              text: `*Payment status:*\\n${\"`\"}${resource.orders.payment_status}${\"`\"}`\n            },\n            {\n              type: \"mrkdwn\",\n              text: `*Fulfillment status:*\\n${\"`\"}${resource.orders.fulfillment_status}${\"`\"}`\n            }\n          ]\n        },\n        {\n          type: \"section\",\n          fields: [\n            {\n              type: \"mrkdwn\",\n              text: `*Shipping address:*\\n${\n                resource.orders.shipping_address !== null\n                  ? resource.orders.shipping_address.full_name + \", \" + resource.orders.shipping_address.full_address\n                  : \"null\"\n              }.`\n            },\n            {\n              type: \"mrkdwn\",\n              text: `*Billing address:*\\n${\n                resource.orders.billing_address !== null\n                  ? resource.orders.billing_address.full_name + \", \" + resource.orders.billing_address.full_address\n                  : \"null\"\n              }.`\n            }\n          ]\n        },\n        {\n          type: \"section\",\n          fields: [\n            {\n              type: \"mrkdwn\",\n              text: `*Payment method:*\\n${\n                resource.orders.payment_method !== null ? resource.orders.payment_method.name : \"null\"\n              }`\n            },\n            {\n              type: \"mrkdwn\",\n              text: `*Shipment number(s):*\\n${\n                resource.orders.shipments.length > 0\n                  ? resource.orders.shipments.map((shipment) => {\n                      if (resource.orders.shipments.length > 1) {\n                        return `\u003Chttps:\u002F\u002Fdashboard.commercelayer.io\u002F${\n                          resource.organizationMode === \"live\" ? \"live\" : \"test\"\n                        }\u002F${resource.organizationSlug}\u002Fresources\u002Fshipments\u002F${shipment.id}|${shipment.number}>, `;\n                      } else {\n                        return `\u003Chttps:\u002F\u002Fdashboard.commercelayer.io\u002F${\n                          resource.organizationMode === \"live\" ? \"live\" : \"test\"\n                        }\u002F${resource.organizationSlug}\u002Fresources\u002Fshipments\u002F${shipment.id}|${shipment.number}>`;\n                      }\n                    })\n                  : \"null\"\n              }`\n            }\n          ]\n        },\n        {\n          type: \"actions\",\n          elements: [\n            resource.orders.status === \"pending\"\n              ? {\n                  type: \"button\",\n                  text: {\n                    type: \"plain_text\",\n                    text: \"Checkout Order\",\n                    emoji: true\n                  },\n                  style: \"primary\",\n                  value: \"checkout_order\",\n                  url: `https:\u002F\u002F${resource.organizationSlug}.checkout.commercelayer.app\u002F${resource.orders.id}?accessToken=${resource.checkoutAccessToken}`,\n                  action_id: \"check_order\"\n                }\n              : {\n                  type: \"button\",\n                  text: {\n                    type: \"plain_text\",\n                    text: \"View Order\",\n                    emoji: true\n                  },\n                  style: \"primary\",\n                  value: \"view_order\",\n                  url: `https:\u002F\u002Fdashboard.commercelayer.io\u002F${resource.organizationMode === \"live\" ? \"live\" : \"test\"}\u002F${\n                    resource.organizationSlug\n                  }\u002Fresources\u002Forders\u002F${resource.orders.id}`,\n                  action_id: \"check_order\"\n                },\n            {\n              type: \"button\",\n              text: {\n                type: \"plain_text\",\n                text: \"View Customer\",\n                emoji: true\n              },\n              value: \"view_customer\",\n              url: `https:\u002F\u002Fdashboard.commercelayer.io\u002F${resource.organizationMode === \"live\" ? \"live\" : \"test\"}\u002F${\n                resource.organizationSlug\n              }\u002Fresources\u002Fcustomers\u002F${resource.orders.customer !== null ? resource.orders.customer.id : \"null\"}`,\n              action_id: \"view_customer\"\n            }\n          ]\n        },\n        {\n          type: \"divider\"\n        },\n        {\n          type: \"context\",\n          elements: [\n            {\n              type: \"image\",\n              image_url: `${triggerUser.user.profile.image_72}`,\n              alt_text: `${triggerUser.user.profile.display_name || triggerUser.user.profile.real_name}'s avatar`\n            },\n            {\n              type: \"mrkdwn\",\n              text: `${\n                triggerUser.user.profile.display_name || triggerUser.user.profile.real_name\n              } has triggered this request.`\n            }\n          ]\n        }\n      ],\n      \u002F\u002F Add a fallback plain text for notifications and accessibility.\n      text: `:shopping_trolley: Order ${\"`\"}${resource.orders.id}${\"`\"} from the *${\n        resource.orders.market.name\n      }* market has a total amount of *${resource.orders.formatted_subtotal_amount}* and was ${\n        resource.orders.placed_at !== null ? \"placed\" : \"created\"\n      } on \u003C!date^${formatTimestamp(\n        resource.orders.placed_at !== null ? resource.orders.placed_at : resource.orders.created_at\n      )}^*{date_long}* at *{time}*|${\n        resource.orders.placed_at !== null ? resource.orders.placed_at : resource.orders.created_at\n      }>.`\n    });\n  }\n};",{"_modelApiKey":367,"copy":507,"listStyle":369,"darkTheme":11,"buttons":508,"color":371,"indent":11},"\u003Cp>PS: You can find the similar code for the \u003Ccode>getReturnResource\u003C\u002Fcode> function in \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fexamples\u002Ftree\u002Fmain\u002Fsolutions\u002Fcommercelayer-slackbot\u002Fapp.ts\">this file\u003C\u002Fa> on GitHub.\u003C\u002Fp>\n\n\u003Chr>\n\n\u003Cp>\u003Cstrong>Here are a couple of things you should note:\u003C\u002Fstrong>\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>Slack provides a \u003Ccode>&lt;!date&gt;\u003C\u002Fcode> \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapi.slack.com\u002Freference\u002Fsurfaces\u002Fformatting#date-formatting\">formatting\u003C\u002Fa> command that formats text containing a date or time and displays them in the local timezone of the person seeing the text. All you need to do is use the syntax: \u003Ccode>&lt;!date^timestamp^token_string^optional_link|fallback_text&gt;\u003C\u002Fcode> and pass in the timestamp in \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FUnix_time\">Unix time format\u003C\u002Fa> (we have a utility function \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fexamples\u002Ftree\u002Fmain\u002Fsolutions\u002Fcommercelayer-slackbot\u002Fsrc\u002Futils\u002FparseDate.ts\">in the GitHub repository\u003C\u002Fa> that converts the datetime attribute from Commerce Layer to that format). Here’s an example: \u003Ccode>&quot;&lt;!date^1514808000^{date_long}|2018-01-01T12:00:00.000Z&quot;\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>Blocks are the building components of a Slack message and can range from text to images to interactive components (like buttons). If you observe, the code above includes some \u003Ccode>blocks: []\u003C\u002Fcode> array with different types of content with text in markdown format. The \u003Ccode>say()\u003C\u002Fcode> method then contains an array of \u003Ccode>blocks\u003C\u002Fcode>, which are transmitted and processed by Slack.\u003C\u002Fli>\n\u003Cli>Slack&#39;s brilliant \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fapp.slack.com\u002Fblock-kit-builder\">Block Kit Builder\u003C\u002Fa> allows you to prototype interactive messages with all the interactive components. The builder lets you (or anyone on your team) mockup messages and generates the corresponding JSON you can paste directly into your app.\u003C\u002Fli>\n\u003Cli>Currently, all Slack interactive buttons dispatch a request (\u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fslackapi\u002Fnode-slack-sdk\u002Fissues\u002F869#issuecomment-554565057\">see this GitHub thread\u003C\u002Fa>); hence you need to respond with 200 OK using the \u003Ccode>action()\u003C\u002Fcode> method and the \u003Ccode>action_id\u003C\u002Fcode> of each button element to avoid a warning icon error when the button is clicked. If the button isn’t a link button (maybe the button triggers some other kind of action), then you will use the same method to acknowledge the button request.\u003C\u002Fli>\n\u003C\u002Fol>\n",[],{"_modelApiKey":409,"code":510,"showLineNumbers":11,"highlightLines":280,"language":429,"size":412},"app.action(\"view_customer\", ({ ack }) => ack());\napp.action(\"check_order\", ({ ack }) => ack());\napp.action(\"view_return\", ({ ack }) => ack());",{"_modelApiKey":367,"copy":512,"listStyle":369,"darkTheme":11,"buttons":513,"color":371,"indent":11},"\u003Ch3>Testing everything!\u003C\u002Fh3>\n\n\u003Cp>Now let’s test for the order resource. If you enter \u003Ccode>\u002Fcl order wJJKhAcvBO\u003C\u002Fcode> passing in an order ID, the returned response will look like this (the images below) for both light and dark themes.\u003C\u002Fp>\n",[],{"addBorderTop":303,"title":515,"firstColumnWidth":516,"gutter":517,"align":518,"darkTheme":11,"reverseOrderAtMobile":11,"columns":519,"_modelApiKey":535},"Commerce Layer Slackbot demo images","1\u002F2","regular","start",[520,528],{"_modelApiKey":373,"border":7,"caption":280,"image":521,"size":382,"darkTheme":11,"rounded":7,"color":371},{"url":522,"alt":523,"title":523,"width":524,"height":525,"format":379,"focalPoint":526,"customData":527},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1680541015-screenshot-2023-03-20-at-19-25-52.png","Commerce Layer Slackbot demo",1970,1292,{"x":292,"y":292},{},{"_modelApiKey":373,"border":7,"caption":280,"image":529,"size":382,"darkTheme":11,"rounded":7,"color":371},{"url":530,"alt":523,"title":523,"width":531,"height":532,"format":379,"focalPoint":533,"customData":534},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1680541041-screenshot-2023-03-20-at-19-26-51.png",1966,1284,{"x":292,"y":292},{},"multi_column_block",{"_modelApiKey":367,"copy":537,"listStyle":369,"darkTheme":11,"buttons":538,"color":371,"indent":11},"\u003Cp>Clicking the “Checkout Order” button will take you to a beautiful checkout page where you can complete the \u003Ccode>pending\u003C\u002Fcode> order. Isn’t this cool? Checkout anywhere powered by Commerce Layer!\u003C\u002Fp>\n\n\u003Cp>As mentioned earlier, this Slackbot can be useful in different ways! Even though this should work for only authorized users (admin or staff), you can extend the bot to customers and have them checkout unfinished orders right from Slack (if you have a community for your ecommerce business). Now you can think of other use cases based on your existing workflows and needs and bring them to life!\u003C\u002Fp>\n",[],{"_modelApiKey":373,"border":11,"caption":280,"image":540,"size":382,"darkTheme":11,"rounded":7,"color":371},{"url":541,"alt":542,"title":542,"width":543,"height":544,"format":379,"focalPoint":545,"customData":546},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1680541340-screenshot-2023-03-20-at-19-29-49.png","Commerce Layer hosted-checkout demo",2688,1774,{"x":292,"y":292},{},{"_modelApiKey":367,"copy":548,"listStyle":369,"darkTheme":11,"buttons":549,"color":371,"indent":11},"\u003Cp>The JSON payload sent back to Slack would look something like this:\u003C\u002Fp>\n",[],{"_modelApiKey":409,"code":551,"showLineNumbers":11,"highlightLines":280,"language":435,"size":482},"{\n    \"token\": \"l8V3x7zxuHhHELLOXSrlpif3\",\n    \"team_id\": \"T34NRBRSLK9\",\n    \"context_team_id\": \"T34NRBRSLK9\",\n    \"context_enterprise_id\": null,\n    \"api_app_id\": \"A04NTNAWORL\",\n    \"event\": {\n        \"bot_id\": \"B04PXV400EU\",\n        \"type\": \"message\",\n        \"text\": \":shopping_trolley: Order `wJZYhAmvBO` from the *USA* market has a total amount of *$102.00* and was created on \u003C!date^1676918681^*{date_long}* at *{time}*|2023-02-20T18:44:41.309Z>.\",\n        \"user\": \"U04NTNKLPPEK\",\n        \"ts\": \"1679328956.227089\",\n        \"app_id\": \"A04NTNAWORL\",\n        \"blocks\": [\n            {\n                \"type\": \"section\",\n                \"block_id\": \"U\u002FUMb\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": \":shopping_trolley: Order `wJZYhAmvBO` from the *USA* market has a total amount of *$102.00* and was created on \u003C!date^1676918681^{date_long} at {time}|2023-02-20T18:44:41.309Z>. Here's a quick summary of the resource:\",\n                    \"verbatim\": false\n                }\n            },\n            {\n                \"type\": \"divider\",\n                \"block_id\": \"GTpp\"\n            },\n            {\n                \"type\": \"section\",\n                \"block_id\": \"O6XY\",\n                \"fields\": [\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Customer email:*\\n\u003Cmailto:aeafury@gmail.com|aeafury@gmail.com>\",\n                        \"verbatim\": false\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Customer ID:*\\n\u003Chttps:\u002F\u002Fdashboard.commercelayer.io\u002Ftest\u002Fcake-store\u002Fresources\u002Fcustomers\u002FkNXNhbDNmB|kNXNhbDNmB>\",\n                        \"verbatim\": false\n                    }\n                ]\n            },\n            {\n                \"type\": \"section\",\n                \"block_id\": \"gQX\",\n                \"fields\": [\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Order number:*\\n`34085857`\",\n                        \"verbatim\": false\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Order status:*\\n`pending`\",\n                        \"verbatim\": false\n                    }\n                ]\n            },\n            {\n                \"type\": \"section\",\n                \"block_id\": \"lB6\",\n                \"fields\": [\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Payment status:*\\n`unpaid`\",\n                        \"verbatim\": false\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Fulfillment status:*\\n`unfulfilled`\",\n                        \"verbatim\": false\n                    }\n                ]\n            },\n            {\n                \"type\": \"section\",\n                \"block_id\": \"n3Si\",\n                \"fields\": [\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Shipping address:*\\nErdaulet Adikhanov , Zhetisay, Home Home, 160500 Turkistan Please select a region, state or province. (KZ) 87059829273.\",\n                        \"verbatim\": false\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Billing address:*\\nErdaulet Adikhanov , Zhetisay, Home Home, 160500 Turkistan Please select a region, state or province. (KZ) 87059829273.\",\n                        \"verbatim\": false\n                    }\n                ]\n            },\n            {\n                \"type\": \"section\",\n                \"block_id\": \"YnfYw\",\n                \"fields\": [\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Payment method:*\\nnull\",\n                        \"verbatim\": false\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*Shipment number(s):*\\n\u003Chttps:\u002F\u002Fdashboard.commercelayer.io\u002Ftest\u002Fcake-store\u002Fresources\u002Fshipments\u002FYkEzCELVRJ|34085857\u002FS\u002F001>\",\n                        \"verbatim\": false\n                    }\n                ]\n            },\n            {\n                \"type\": \"actions\",\n                \"block_id\": \"=mnr1\",\n                \"elements\": [\n                    {\n                        \"type\": \"button\",\n                        \"action_id\": \"check_order\",\n                        \"text\": {\n                            \"type\": \"plain_text\",\n                            \"text\": \"Checkout Order\",\n                            \"emoji\": true\n                        },\n                        \"style\": \"primary\",\n                        \"value\": \"checkout_order\",\n                        \"url\": \"https:\u002F\u002Fcake-store.checkout.commercelayer.app\u002FwJZYhAmvBO?accessToken=eyJhbGciOiJIUzUxMiJ9.eyJvcmdhbml6YXRpb24iOnsiaWQiOiJFbnBWT0ZyS3Z5Iiwic2x1ZyI6ImNha2Utc3RvcmUiLCJlbnRlcnByaXNlIjpmYWxzZX0sImFwcGxpY2F0aW9uIjp7ImlkIjoibHBKcW5pSlhkTSIsImtpbmQiOiJzYWxlc19jaGFubmVsIiwicHVibGljIjp0cnVlfSwidGVzdCI6dHJ1ZSwiZXhwIjoxNzEwODY0OTU1LCJtYXJrZXQiOnsiaWQiOlsiWWxxeEdoS3JRZyJdLCJwcmljZV9saXN0X2lkIjoid2xacnZDWXJLbCIsInN0b2NrX2xvY2F0aW9uX2lkcyI6WyJ6bkJqeHVsTFZuIiwiWmtZcVh1ZFZkayJdLCJnZW9jb2Rlcl9pZCI6bnVsbCwiYWxsb3dzX2V4dGVybmFsX3ByaWNlcyI6ZmFsc2V9LCJyYW5kIjowLjY1MzEyNjIwMzIyNTY5MjV9.7iU1rCXijtBQ8LCuluPYCXKKYjXUIGz_FEfbgOSyAJL5f9bILtclWg3uoUuE9tI5VKgj_teFiKBSZOVyMEFchA\"\n                    },\n                    {\n                        \"type\": \"button\",\n                        \"action_id\": \"view_customer\",\n                        \"text\": {\n                            \"type\": \"plain_text\",\n                            \"text\": \"View Customer\",\n                            \"emoji\": true\n                        },\n                        \"value\": \"view_customer\",\n                        \"url\": \"https:\u002F\u002Fdashboard.commercelayer.io\u002Ftest\u002Fcake-store\u002Fresources\u002Fcustomers\u002FkNXNhbDNmB\"\n                    }\n                ]\n            },\n            {\n                \"type\": \"divider\",\n                \"block_id\": \"GnGf\"\n            },\n            {\n                \"type\": \"context\",\n                \"block_id\": \"DXTjR\",\n                \"elements\": [\n                    {\n                        \"type\": \"image\",\n                        \"image_url\": \"https:\u002F\u002Fsecure.gravatar.com\u002Favatar\u002F551d7dcacd90143bd0cbe29ac68c212c.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0024-72.png\",\n                        \"alt_text\": \"bolaji-bot's avatar\"\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"bolaji-bot has triggered this request.\",\n                        \"verbatim\": false\n                    }\n                ]\n            }\n        ],\n        \"team\": \"T04NTHRSKK9\",\n        \"bot_profile\": {\n            \"id\": \"B04PXV323EU\",\n            \"deleted\": false,\n            \"name\": \"Commerce Layer Bot\",\n            \"updated\": 1678213218,\n            \"app_id\": \"A04NTNAPX2B\",\n            \"icons\": {\n                \"image_36\": \"https:\u002F\u002Favatars.slack-edge.com\u002F2023-02-12\u002F4790294597874_713e6192e0862a34e839_36.png\",\n                \"image_48\": \"https:\u002F\u002Favatars.slack-edge.com\u002F2023-02-12\u002F4790294597874_713e6192e0862a34e839_48.png\",\n                \"image_72\": \"https:\u002F\u002Favatars.slack-edge.com\u002F2023-02-12\u002F4790294597874_713e6192e0862a34e839_72.png\"\n            },\n            \"team_id\": \"T34NRBRSLK9\"\n        },\n        \"channel\": \"C04P5617VFC\",\n        \"event_ts\": \"1679328956.227089\",\n        \"channel_type\": \"channel\"\n    },\n    \"type\": \"event_callback\",\n    \"event_id\": \"Ev04UCFFK6KZ\",\n    \"event_time\": 1679328956,\n    \"authorizations\": [\n        {\n            \"enterprise_id\": null,\n            \"team_id\": \"T34NRBRSLK9\",\n            \"user_id\": \"U04NTNKLPPEK\",\n            \"is_bot\": true,\n            \"is_enterprise_install\": false\n        }\n    ],\n    \"is_ext_shared_channel\": false,\n    \"event_context\": \"4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUMDROVEhSU0tLOSIsImFpZCI6IkEwNE5UTkFQWDJCIiwiY2lkIjoiQzA0UDg0MTdWRkMifQ\"\n}",{"_modelApiKey":367,"copy":553,"listStyle":369,"darkTheme":11,"buttons":554,"color":371,"indent":11},"\u003Ch3>Conclusion\u003C\u002Fh3>\n\n\u003Cp>And that’s it! We hope you found this tutorial helpful and that you feel confident about building any Slack bot. So far, we have been able to implement a Slack app and write all the business logic we need in development. The next phase is to prepare the application for distribution by adding more features that will allow users to install the Slack app into their Slack workspace from the Slack App Directory or a unique installation link. In the next tutorial, we will show you how to set up Slack OAuth installation, store Slack and the app user&#39;s data in a database using Supabase, and deploy the entire application. We will also make the Commerce Layer Slackbot open for test public downloads then. Brace up! There’s more fun coming up soon :).\u003C\u002Fp>\n\n\u003Cp>If you need it, the final manifest file with all the configured scopes can be found in \u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fexamples\u002Ftree\u002Fmain\u002Fsolutions\u002Fcommercelayer-slackbot\u002Fmanifests\">this GitHub repository\u003C\u002Fa>. You can use it to debug your own app or replicate the same configuration options we used for the Commerce Layer Bot. Cheers!\u003C\u002Fp>\n",[],{"_modelApiKey":556,"title":557,"preTitle":558,"linkText":559,"darkTheme":11,"link":560},"link_block","Commerce Layer Slackbot","Open-source","View on GitHub",{"_modelApiKey":203,"text":561,"label":280,"subtitle":280,"icon":280,"isExternal":7,"url":562,"link":303,"anchor":280,"openForm":11,"form":303,"htmlClassNames":280},"Commerce Layer Slackbot GitHub Repo","https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fexamples\u002Ftree\u002Fmain\u002Fsolutions\u002Fcommercelayer-slackbot",{"displayTitle":564,"cta":303,"ctaText":280,"cards":565},"Your next read",[566,581,596],{"_modelApiKey":212,"link":567,"subtitle":571,"date":571,"author":572,"additionalAuthors":576,"icon":577,"abstract":578,"tag":579,"displayTitle":568,"cardRatio":300,"featured":580},{"text":568,"isExternal":11,"url":280,"link":569},"Getting started with Commerce Layer JavaScript SDK.",{"_modelApiKey":212,"slug":570},"getting-started-with-commerce-layer-javascript-sdk","April 14, 2022",{"name":285,"picture":573},{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":574,"customData":575},{"x":292,"y":292},{},[],"code-block","Commerce Layer provides a JavaScript SDK that makes it quick and easy for developers to interact with our commerce APIs. In this article, we’ll look at some fundamental technical details about how it works.",{"color":298,"name":299},0,{"_modelApiKey":212,"link":582,"subtitle":586,"date":586,"author":587,"additionalAuthors":591,"icon":592,"abstract":593,"tag":594,"displayTitle":583,"cardRatio":11,"featured":595},{"text":583,"isExternal":11,"url":280,"link":584},"Building an external payment gateway with the Twitter API.",{"_modelApiKey":212,"slug":585},"building-an-external-payment-gateway-with-twitter-api","August 17, 2022",{"name":285,"picture":588},{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":589,"customData":590},{"x":292,"y":292},{},[],"twitter-logo","We makes integrating with any local payment gateway like Twitter possible, allowing you to explore any option your customers demand. In this article, you will learn how to get started with our payment gateways, external payment gateways, and payment methods.",{"color":298,"name":299},1,{"_modelApiKey":212,"link":597,"subtitle":601,"date":601,"author":602,"additionalAuthors":609,"icon":610,"abstract":611,"tag":612,"displayTitle":598,"cardRatio":11,"featured":38},{"text":598,"isExternal":11,"url":280,"link":599},"Finding unmined opportunities in your checkout.",{"_modelApiKey":212,"slug":600},"how-to-optimize-checkout","February 28, 2023",{"name":603,"picture":604},"Barbara Rybka",{"url":605,"alt":606,"title":606,"width":289,"height":289,"format":290,"focalPoint":607,"customData":608},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1754320129-barbara.jpg","Commerce Layer avatar",{"x":292,"y":292},{},[],"shopping-cart","Removing friction from checkout will increase conversions and lift ROI of the marketing programs that bring traffic to your site. ",{"color":298,"name":100},[614,615,617,619,621,623,625,627,629,631,633,635,637,639,641,643,645],{"attributes":303,"tag":304,"content":305},{"attributes":616,"tag":309,"content":303},{"property":308,"content":305},{"attributes":618,"tag":309,"content":303},{"name":312,"content":305},{"attributes":620,"tag":309,"content":303},{"name":315,"content":316},{"attributes":622,"tag":309,"content":303},{"property":319,"content":316},{"attributes":624,"tag":309,"content":303},{"name":322,"content":316},{"attributes":626,"tag":309,"content":303},{"property":325,"content":326},{"attributes":628,"tag":309,"content":303},{"property":329,"content":330},{"attributes":630,"tag":309,"content":303},{"property":333,"content":334},{"attributes":632,"tag":309,"content":303},{"name":337,"content":326},{"attributes":634,"tag":309,"content":303},{"property":340,"content":341},{"attributes":636,"tag":309,"content":303},{"property":344,"content":345},{"attributes":638,"tag":309,"content":303},{"property":348,"content":349},{"attributes":640,"tag":309,"content":303},{"property":352,"content":353},{"attributes":642,"tag":309,"content":303},{"property":356,"content":280},{"attributes":644,"tag":309,"content":303},{"name":359,"content":360},{"attributes":646,"tag":309,"content":303},{"name":363,"content":364},[648],{"name":285,"picture":649},{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":650,"customData":651},{"x":292,"y":292},{},1790593730457]