Repository: slack-go/slack
Pull request #772 (2020-08-09 11:37:08)

Add response metadata to slack response
## Summary of Changes
The Web API response represented by the `SlackResponse` struct can contain an additional JSON field `"response_metadata"` in such cases as block validation failure, etc. It would be helpful to include the field in the struct so that these extra descriptors can be returned in existing models like `ViewResponse` when something goes wrong.

## Example
Below are two examples of what the `ViewResponse` looks like when dumped before/after the addition.
 
**Before**
```
(*slack.ViewResponse)(0xc000419320)({
   SlackResponse: (slack.SlackResponse) {
       Ok: (bool) false,
       Error: (string) (len=17) "invalid_arguments"
    },
    ...
}
```

**After**
```
(*slack.ViewResponse)(0xc000778500)({
    SlackResponse: (slack.SlackResponse) {
        Ok: (bool) false,                                                                                                                                            
        Error: (string) (len=17) "invalid_arguments",                                                                                                                
        ResponseMetadata: (slack.ResponseMetadata) {                                                                                                                 
           Cursor: (string) "",                                                                                                                                        
           Messages: ([]string) (len=3 cap=4) {                                                                                                                        
              (string) (len=64) "[ERROR] failed to match all allowed schemas [json-pointer:/view]",                                                                      
              (string) (len=70) "[ERROR] failed to match all allowed schemas [json-pointer:/view/title]",                                                                
              (string) (len=71) "[ERROR] must be less than 25 characters [json-pointer:/view/title/text]"                                                                
           },                                                                                                                                                          
           Warnings: ([]string) (len=1 cap=4) {                                                                                                                        
             (string) (len=15) "missing_charset"                                                                                                                        
           }                                                                                                                                                           
        } 
    },
    ...
}
```


Patch:
diff --git a/attachments.go b/attachments.go
index 5f388e9..b5b79f9 100644
--- a/attachments.go
+++ b/attachments.go
@@ -75,7 +75,7 @@ type Attachment struct {
 	Title     string `json:"title,omitempty"`
 	TitleLink string `json:"title_link,omitempty"`
 	Pretext   string `json:"pretext,omitempty"`
-	Text      string `json:"text"` // Required
+	Text      string `json:"text,omitempty"`
 
 	ImageURL string `json:"image_url,omitempty"`
 	ThumbURL string `json:"thumb_url,omitempty"`
diff --git a/block.go b/block.go
index dd4b871..4b4b155 100644
--- a/block.go
+++ b/block.go
@@ -16,7 +16,6 @@ const (
 	MBTContext MessageBlockType = "context"
 	MBTFile    MessageBlockType = "file"
 	MBTInput   MessageBlockType = "input"
-	MBTHeader  MessageBlockType = "header"
 )
 
 // Block defines an interface all block types should implement
diff --git a/block_header.go b/block_header.go
deleted file mode 100644
index 6dff4b8..0000000
--- a/block_header.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package slack
-
-// HeaderBlock defines a new block of type header
-//
-// More Information: https://api.slack.com/reference/messaging/blocks#header
-type HeaderBlock struct {
-	Type    MessageBlockType `json:"type"`
-	Text    *TextBlockObject `json:"text,omitempty"`
-	BlockID string           `json:"block_id,omitempty"`
-}
-
-// BlockType returns the type of the block
-func (s HeaderBlock) BlockType() MessageBlockType {
-	return s.Type
-}
-
-// HeaderBlockOption allows configuration of options for a new header block
-type HeaderBlockOption func(*HeaderBlock)
-
-func HeaderBlockOptionBlockID(blockID string) HeaderBlockOption {
-	return func(block *HeaderBlock) {
-		block.BlockID = blockID
-	}
-}
-
-// NewHeaderBlock returns a new instance of a header block to be rendered
-func NewHeaderBlock(textObj *TextBlockObject, options ...HeaderBlockOption) *HeaderBlock {
-	block := HeaderBlock{
-		Type: MBTHeader,
-		Text: textObj,
-	}
-
-	for _, option := range options {
-		option(&block)
-	}
-
-	return &block
-}
diff --git a/errors.go b/errors.go
index 8be22a6..a1dfec2 100644
--- a/errors.go
+++ b/errors.go
@@ -9,7 +9,6 @@ const (
 	ErrRTMGoodbye           = errorsx.String("goodbye detected")
 	ErrRTMDeadman           = errorsx.String("deadman switch triggered")
 	ErrParametersMissing    = errorsx.String("received empty parameters")
-	ErrBlockIDNotUnique     = errorsx.String("Block ID needs to be unique")
 	ErrInvalidConfiguration = errorsx.String("invalid configuration")
 	ErrMissingHeaders       = errorsx.String("missing headers")
 	ErrExpiredTimestamp     = errorsx.String("timestamp is too old")
diff --git a/examples/buttons/buttons.go b/examples/buttons/buttons.go
index 438324f..a336044 100644
--- a/examples/buttons/buttons.go
+++ b/examples/buttons/buttons.go
@@ -39,7 +39,6 @@ func main() {
 	}
 	fmt.Printf("Message with buttons sucessfully sent to channel %s at %s", channelID, timestamp)
 	http.HandleFunc("/actions", actionHandler)
-	http.ListenAndServe(":3000", nil)
 }
 
 func actionHandler(w http.ResponseWriter, r *http.Request) {
diff --git a/examples/dialog/dialog.go b/examples/dialog/dialog.go
index 4b4d6ab..e964a86 100644
--- a/examples/dialog/dialog.go
+++ b/examples/dialog/dialog.go
@@ -52,7 +52,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
 	str = strings.Replace(str, "payload=", "", 1)
 	var message slack.InteractionCallback
 	if err := json.Unmarshal([]byte(str), &message); err != nil {
-		log.Printf("[ERROR] Fail to unmarshal json: %v", err)
+		log.Printf("[ERROR] Fail to unmarchal json: %v", err)
 		return
 	}
 
diff --git a/examples/eventsapi/events.go b/examples/eventsapi/events.go
index 9bf1850..3dde1bb 100644
--- a/examples/eventsapi/events.go
+++ b/examples/eventsapi/events.go
@@ -18,7 +18,7 @@ func main() {
 		buf := new(bytes.Buffer)
 		buf.ReadFrom(r.Body)
 		body := buf.String()
-		eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: "VERIFICATION_TOKEN"}))
+		eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: "TOKEN"}))
 		if e != nil {
 			w.WriteHeader(http.StatusInternalServerError)
 			return
@@ -29,7 +29,6 @@ func main() {
 			err := json.Unmarshal([]byte(body), &r)
 			if err != nil {
 				w.WriteHeader(http.StatusInternalServerError)
-				return
 			}
 			w.Header().Set("Content-Type", "text")
 			w.Write([]byte(r.Challenge))
diff --git a/examples/messages/messages.go b/examples/messages/messages.go
index dd7f02d..20e8f19 100644
--- a/examples/messages/messages.go
+++ b/examples/messages/messages.go
@@ -22,12 +22,7 @@ func main() {
 		*/
 	}
 
-	channelID, timestamp, err := api.PostMessage(
-		"CHANNEL_ID",
-		slack.MsgOptionText("Some text", false),
-		slack.MsgOptionAttachments(attachment),
-		slack.MsgOptionAsUser(true), // Add this if you want that the bot would post message as a user, otherwise it will send response using the default slackbot
-	)
+	channelID, timestamp, err := api.PostMessage("CHANNEL_ID", slack.MsgOptionText("Some text", false), slack.MsgOptionAttachments(attachment))
 	if err != nil {
 		fmt.Printf("%s\n", err)
 		return
diff --git a/examples/modal/modal.go b/examples/modal/modal.go
index 78bb60e..161cfbf 100644
--- a/examples/modal/modal.go
+++ b/examples/modal/modal.go
@@ -1,27 +1,14 @@
-// Modal example - How to respond to a slash command with an interactive modal and parse the response
-// The flow of this example:
-// 1. User trigers your app with a slash command (e.g. /modaltest) that will send a request to http://URL/slash and respond with a request to open a modal
-// 2. User fills out fields first and last name in modal and hits submit
-// 3. This will send a request to http://URL/modal and send a greeting message to the user
-
-// Note: Within your slack app you will need to enable and provide a URL for "Interactivity & Shortcuts" and "Slash Commands"
-// Note: Be sure to update YOUR_SIGNING_SECRET_HERE and YOUR_TOKEN_HERE
-// You can use ngrok to test this example: https://api.slack.com/tutorials/tunneling-with-ngrok
-// Helpful slack documentation to learn more: https://api.slack.com/interactivity/handling
-
 package main
 
 import (
-	"bytes"
-	"encoding/json"
 	"fmt"
-	"io/ioutil"
-	"net/http"
 
 	"github.com/slack-go/slack"
 )
 
-func generateModalRequest() slack.ModalViewRequest {
+// A minimal example showing how to open a modal
+func main() {
+
 	// Create a ModalViewRequest with a header and two inputs
 	titleText := slack.NewTextBlockObject("plain_text", "My App", false, false)
 	closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
@@ -38,7 +25,7 @@ func generateModalRequest() slack.ModalViewRequest {
 
 	lastNameText := slack.NewTextBlockObject("plain_text", "Last Name", false, false)
 	lastNamePlaceholder := slack.NewTextBlockObject("plain_text", "Enter your first name", false, false)
-	lastNameElement := slack.NewPlainTextInputBlockElement(lastNamePlaceholder, "lastName")
+	lastNameElement := slack.NewPlainTextInputBlockElement(lastNamePlaceholder, "firstName")
 	lastName := slack.NewInputBlock("Last Name", lastNameText, lastNameElement)
 
 	blocks := slack.Blocks{
@@ -55,102 +42,14 @@ func generateModalRequest() slack.ModalViewRequest {
 	modalRequest.Close = closeText
 	modalRequest.Submit = submitText
 	modalRequest.Blocks = blocks
-	return modalRequest
-}
-
-// This was taken from the slash example
-// https://github.com/slack-go/slack/blob/master/examples/slash/slash.go
-func verifySigningSecret(r *http.Request) error {
-	signingSecret := "YOUR_SIGNING_SECRET_HERE"
-	verifier, err := slack.NewSecretsVerifier(r.Header, signingSecret)
-	if err != nil {
-		fmt.Println(err.Error())
-		return err
-	}
-
-	body, err := ioutil.ReadAll(r.Body)
-	if err != nil {
-		fmt.Println(err.Error())
-		return err
-	}
-	// Need to use r.Body again when unmarshalling SlashCommand and InteractionCallback
-	r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
-
-	verifier.Write(body)
-	if err = verifier.Ensure(); err != nil {
-		fmt.Println(err.Error())
-		return err
-	}
-
-	return nil
-}
-
-func handleSlash(w http.ResponseWriter, r *http.Request) {
-
-	err := verifySigningSecret(r)
-	if err != nil {
-		fmt.Printf(err.Error())
-		w.WriteHeader(http.StatusUnauthorized)
-		return
-	}
-
-	s, err := slack.SlashCommandParse(r)
-	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Println(err.Error())
-		return
-	}
-
-	switch s.Command {
-	case "/humboldttest":
-		api := slack.New("YOUR_TOKEN_HERE")
-		modalRequest := generateModalRequest()
-		_, err = api.OpenView(s.TriggerID, modalRequest)
-		if err != nil {
-			fmt.Printf("Error opening view: %s", err)
-		}
-	default:
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-}
-
-func handleModal(w http.ResponseWriter, r *http.Request) {
-
-	err := verifySigningSecret(r)
-	if err != nil {
-		fmt.Printf(err.Error())
-		w.WriteHeader(http.StatusUnauthorized)
-		return
-	}
-
-	var i slack.InteractionCallback
-	err = json.Unmarshal([]byte(r.FormValue("payload")), &i)
-	if err != nil {
-		fmt.Printf(err.Error())
-		w.WriteHeader(http.StatusUnauthorized)
-		return
-	}
 
-	// Note there might be a better way to get this info, but I figured this structure out from looking at the json response
-	firstName := i.View.State.Values["First Name"]["firstName"].Value
-	lastName := i.View.State.Values["Last Name"]["lastName"].Value
+	api := slack.New("YOUR_BOT_TOKEN_HERE")
 
-	msg := fmt.Sprintf("Hello %s %s, nice to meet you!", firstName, lastName)
-
-	api := slack.New("YOUR_TOKEN_HERE")
-	_, _, err = api.PostMessage(i.User.ID,
-		slack.MsgOptionText(msg, false),
-		slack.MsgOptionAttachments())
+	// Using a trigger ID you can open a modal
+	// The trigger ID is provided through certain events and interactions
+	// More information can be found here: https://api.slack.com/interactivity/handling#modal_responses
+	_, err := api.OpenView("YOUR_TRIGGERID_HERE", modalRequest)
 	if err != nil {
-		fmt.Printf(err.Error())
-		w.WriteHeader(http.StatusUnauthorized)
-		return
+		fmt.Printf("Error opening view: %s", err)
 	}
 }
-
-func main() {
-	http.HandleFunc("/slash", handleSlash)
-	http.HandleFunc("/modal", handleModal)
-	http.ListenAndServe(":4390", nil)
-}
diff --git a/examples/modal_users/users.go b/examples/modal_users/users.go
deleted file mode 100644
index 4a6ef90..0000000
--- a/examples/modal_users/users.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package main
-
-import (
-	"fmt"
-
-	"github.com/slack-go/slack"
-)
-
-// An example how to open a modal with different kinds of input fields
-func main() {
-
-	// Create a ModalViewRequest with a header and two inputs
-	titleText := slack.NewTextBlockObject(slack.PlainTextType, "Create channel demo", false, false)
-	closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false)
-	submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false)
-
-	contextText := slack.NewTextBlockObject(slack.MarkdownType, "This app demonstrates the use of different fields", false, false)
-	contextBlock := slack.NewContextBlock("context", contextText)
-
-	// Only the inputs in input blocks will be included in view_submission’s view.state.values: https://slack.dev/java-slack-sdk/guides/modals
-	// This means the inputs will not be interactive either because they do not trigger block_actions messages: https://api.slack.com/surfaces/modals/using#interactions
-	channelNameText := slack.NewTextBlockObject(slack.PlainTextType, "Channel Name", false, false)
-	channelPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "New channel name", false, false)
-	channelNameElement := slack.NewPlainTextInputBlockElement(channelPlaceholder, "channel_name")
-	// Slack channel names can be maximum 80 characters: https://api.slack.com/methods/conversations.create
-	channelNameElement.MaxLength = 80
-	channelNameBlock := slack.NewInputBlock("channel_name", channelNameText, channelNameElement)
-	channelNameBlock.Hint = slack.NewTextBlockObject(slack.PlainTextType, "Channel names may only contain lowercase letters, numbers, hyphens, and underscores, and must be 80 characters or less", false, false)
-
-	// Provide a static list of users to choose from, those provided now are just made up user IDs
-	// Get user IDs by right clicking on them in Slack, select "Copy link", and inspect the last part of the link
-	// The user ID should start with "U" followed by 8 random characters
-	memberOptions := createOptionBlockObjects([]string{"U9911MMAA", "U2233KKNN", "U00112233"}, true)
-	inviteeText := slack.NewTextBlockObject(slack.PlainTextType, "Invitee from static list", false, false)
-	inviteeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, nil, "invitee", memberOptions...)
-	inviteeBlock := slack.NewInputBlock("invitee", inviteeText, inviteeOption)
-
-	additionalInviteeText := slack.NewTextBlockObject(slack.PlainTextType, "Invitee from complete list of users", false, false)
-	additionalInviteeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeUser, additionalInviteeText, "")
-	additionalInviteeSection := slack.NewSectionBlock(additionalInviteeText, nil, slack.NewAccessory(additionalInviteeOption))
-
-	checkboxTxt := slack.NewTextBlockObject(slack.PlainTextType, "Checkbox", false, false)
-	checkboxOptions := createOptionBlockObjects([]string{"option 1", "option 2", "option 3"}, false)
-	checkboxOptionsBlock := slack.NewCheckboxGroupsBlockElement("chkbox", checkboxOptions...)
-	checkboxBlock := slack.NewInputBlock("chkbox", checkboxTxt, checkboxOptionsBlock)
-
-	summaryText := slack.NewTextBlockObject(slack.PlainTextType, "Summary", false, false)
-	summaryPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Summary of reason for creating channel", false, false)
-	summaryElement := slack.NewPlainTextInputBlockElement(summaryPlaceholder, "summary")
-	// Just set an arbitrary max length to avoid too prose summary
-	summaryElement.MaxLength = 200
-	summaryElement.Multiline = true
-	summaryBlock := slack.NewInputBlock("summary", summaryText, summaryElement)
-
-	blocks := slack.Blocks{
-		BlockSet: []slack.Block{
-			contextBlock,
-			channelNameBlock,
-			inviteeBlock,
-			additionalInviteeSection,
-			checkboxBlock,
-			summaryBlock,
-		},
-	}
-
-	var modalRequest slack.ModalViewRequest
-	modalRequest.Type = slack.ViewType("modal")
-	modalRequest.Title = titleText
-	modalRequest.Close = closeText
-	modalRequest.Submit = submitText
-	modalRequest.Blocks = blocks
-	modalRequest.CallbackID = "create_channel"
-
-	api := slack.New("YOUR_BOT_TOKEN_HERE")
-
-	// Using a trigger ID you can open a modal
-	// The trigger ID is provided through certain events and interactions
-	// More information can be found here: https://api.slack.com/interactivity/handling#modal_responses
-	_, err := api.OpenView("YOUR_TRIGGERID_HERE", modalRequest)
-	if err != nil {
-		fmt.Printf("Error opening view: %s", err)
-	}
-}
-
-// createOptionBlockObjects - utility function for generating option block objects
-func createOptionBlockObjects(options []string, users bool) []*slack.OptionBlockObject {
-	optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
-	var text string
-	for _, o := range options {
-		if users {
-			text = fmt.Sprintf("<@%s>", o)
-		} else {
-			text = o
-		}
-		optionText := slack.NewTextBlockObject(slack.PlainTextType, text, false, false)
-		optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(o, optionText))
-	}
-	return optionBlockObjects
-}
diff --git a/go.sum b/go.sum
index a66560a..d01bacb 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
 github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
-github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
-github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
diff --git a/messages.go b/messages.go
index 051ae67..09db546 100644
--- a/messages.go
+++ b/messages.go
@@ -46,10 +46,9 @@ type Msg struct {
 	EventTimestamp   string `json:"event_ts,omitempty"`
 
 	// bot_message (https://api.slack.com/events/message/bot_message)
-	BotID      string      `json:"bot_id,omitempty"`
-	Username   string      `json:"username,omitempty"`
-	Icons      *Icon       `json:"icons,omitempty"`
-	BotProfile *BotProfile `json:"bot_profile,omitempty"`
+	BotID    string `json:"bot_id,omitempty"`
+	Username string `json:"username,omitempty"`
+	Icons    *Icon  `json:"icons,omitempty"`
 
 	// channel_join, group_join
 	Inviter string `json:"inviter,omitempty"`
@@ -113,17 +112,6 @@ type Icon struct {
 	IconEmoji string `json:"icon_emoji,omitempty"`
 }
 
-// BotProfile contains information about a bot
-type BotProfile struct {
-	AppID   string `json:"app_id,omitempty"`
-	Deleted bool   `json:"deleted,omitempty"`
-	Icons   *Icons `json:"icons,omitempty"`
-	ID      string `json:"id,omitempty"`
-	Name    string `json:"name,omitempty"`
-	TeamID  string `json:"team_id,omitempty"`
-	Updated int64  `json:"updated,omitempty"`
-}
-
 // Edited indicates that a message has been edited.
 type Edited struct {
 	User      string `json:"user,omitempty"`
diff --git a/misc.go b/misc.go
index 0dcee95..a5ca51b 100644
--- a/misc.go
+++ b/misc.go
@@ -22,8 +22,9 @@ import (
 
 // SlackResponse handles parsing out errors from the web api.
 type SlackResponse struct {
-	Ok    bool   `json:"ok"`
-	Error string `json:"error"`
+	Ok               bool             `json:"ok"`
+	Error            string           `json:"error"`
+	ResponseMetadata ResponseMetadata `json:"response_metadata"`
 }
 
 func (t SlackResponse) Err() error {
diff --git a/oauth.go b/oauth.go
index 707ccc6..4dc23b1 100644
--- a/oauth.go
+++ b/oauth.go
@@ -33,15 +33,14 @@ type OAuthResponse struct {
 
 // OAuthV2Response ...
 type OAuthV2Response struct {
-	AccessToken     string                       `json:"access_token"`
-	TokenType       string                       `json:"token_type"`
-	Scope           string                       `json:"scope"`
-	BotUserID       string                       `json:"bot_user_id"`
-	AppID           string                       `json:"app_id"`
-	Team            OAuthV2ResponseTeam          `json:"team"`
-	IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"`
-	Enterprise      OAuthV2ResponseEnterprise    `json:"enterprise"`
-	AuthedUser      OAuthV2ResponseAuthedUser    `json:"authed_user"`
+	AccessToken string                    `json:"access_token"`
+	TokenType   string                    `json:"token_type"`
+	Scope       string                    `json:"scope"`
+	BotUserID   string                    `json:"bot_user_id"`
+	AppID       string                    `json:"app_id"`
+	Team        OAuthV2ResponseTeam       `json:"team"`
+	Enterprise  OAuthV2ResponseEnterprise `json:"enterprise"`
+	AuthedUser  OAuthV2ResponseAuthedUser `json:"authed_user"`
 	SlackResponse
 }
 
diff --git a/slack.go b/slack.go
index d342a3e..768983b 100644
--- a/slack.go
+++ b/slack.go
@@ -23,7 +23,9 @@ type httpClient interface {
 
 // ResponseMetadata holds pagination metadata
 type ResponseMetadata struct {
-	Cursor string `json:"next_cursor"`
+	Cursor   string   `json:"next_cursor"`
+	Messages []string `json:"messages"`
+	Warnings []string `json:"warnings"`
 }
 
 func (t *ResponseMetadata) initialize() *ResponseMetadata {
diff --git a/slackevents/inner_events.go b/slackevents/inner_events.go
index 4120751..3175e6e 100644
--- a/slackevents/inner_events.go
+++ b/slackevents/inner_events.go
@@ -29,7 +29,7 @@ type AppMentionEvent struct {
 	SourceTeam string `json:"source_team,omitempty"`
 
 	// BotID is filled out when a bot triggers the app_mention event
-	BotID string `json:"bot_id,omitempty"`
+	BotID    string `json:"bot_id,omitempty"`
 }
 
 // AppHomeOpenedEvent Your Slack app home was opened.
@@ -113,13 +113,6 @@ type MessageEvent struct {
 
 	Upload bool   `json:"upload"`
 	Files  []File `json:"files"`
-
-	Attachments []slack.Attachment `json:"attachments,omitempty"`
-
-	// Root is the message that was broadcast to the channel when the SubType is
-	// thread_broadcast. If this is not a thread_broadcast message event, this
-	// value is nil.
-	Root *MessageEvent `json:"root"`
 }
 
 // MemberJoinedChannelEvent A member joined a public or private channel
diff --git a/views.go b/views.go
index ee197ad..c34feec 100644
--- a/views.go
+++ b/views.go
@@ -150,23 +150,6 @@ func (api *Client) OpenView(triggerID string, view ModalViewRequest) (*ViewRespo
 	return api.OpenViewContext(context.Background(), triggerID, view)
 }
 
-// ValidateUniqueBlockID will verify if each input block has a unique block ID if set
-func ValidateUniqueBlockID(view ModalViewRequest) bool {
-
-	uniqueBlockID := map[string]bool{}
-
-	for _, b := range view.Blocks.BlockSet {
-		if inputBlock, ok := b.(*InputBlock); ok {
-			if _, ok := uniqueBlockID[inputBlock.BlockID]; ok {
-				return false
-			}
-			uniqueBlockID[inputBlock.BlockID] = true
-		}
-	}
-
-	return true
-}
-
 // OpenViewContext opens a view for a user with a custom context.
 func (api *Client) OpenViewContext(
 	ctx context.Context,
@@ -176,11 +159,6 @@ func (api *Client) OpenViewContext(
 	if triggerID == "" {
 		return nil, ErrParametersMissing
 	}
-
-	if !ValidateUniqueBlockID(view) {
-		return nil, ErrBlockIDNotUnique
-	}
-
 	req := openViewRequest{
 		TriggerID: triggerID,
 		View:      view,


Test patch:
diff --git a/attachements_test.go b/attachements_test.go
index 24e2254..4b951f1 100644
--- a/attachements_test.go
+++ b/attachements_test.go
@@ -12,7 +12,6 @@ func TestAttachment_UnmarshalMarshalJSON_WithBlocks(t *testing.T) {
 
 	originalAttachmentJson := `{
     "id": 1,
-    "text":"",
     "blocks": [
       {
         "type": "section",
diff --git a/block_header_test.go b/block_header_test.go
deleted file mode 100644
index f1cfb5d..0000000
--- a/block_header_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package slack
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestNewHeaderBlock(t *testing.T) {
-
-	textInfo := NewTextBlockObject("plain_text", "This is quite the header", false, false)
-
-	headerBlock := NewHeaderBlock(textInfo, HeaderBlockOptionBlockID("test_block"))
-	assert.Equal(t, string(headerBlock.Type), "header")
-	assert.Equal(t, string(headerBlock.BlockID), "test_block")
-	assert.Equal(t, headerBlock.Text.Type, "plain_text")
-	assert.Contains(t, headerBlock.Text.Text, "quite the header")
-}
diff --git a/chat_test.go b/chat_test.go
index a038728..450cae7 100644
--- a/chat_test.go
+++ b/chat_test.go
@@ -101,7 +101,7 @@ func TestPostMessage(t *testing.T) {
 					}),
 			},
 			expected: url.Values{
-				"attachments": []string{`[{"text":"","blocks":` + blockStr + `}]`},
+				"attachments": []string{`[{"blocks":` + blockStr + `}]`},
 				"channel":     []string{"CXXX"},
 				"token":       []string{"testing-token"},
 			},
diff --git a/messages_test.go b/messages_test.go
index d47800e..3a05dc8 100644
--- a/messages_test.go
+++ b/messages_test.go
@@ -260,29 +260,11 @@ func TestComment(t *testing.T) {
 var botMessage = `{
     "type": "message",
     "subtype": "bot_message",
+    "ts": "1358877455.000010",
     "text": "Pushing is the answer",
-    "suppress_notification": false,
     "bot_id": "BB12033",
     "username": "github",
-    "icons": {},
-    "team": "T01A9CUMPQA",
-    "bot_profile": {
-        "id": "BB12033",
-        "deleted": false,
-        "name": "github",
-        "updated": 1599574335,
-        "app_id": "A6DB2SWUW",
-        "icons": {
-            "image_36": "https://slack-files2.s3-us-west-2.amazonaws.com/avatars/2017-10-24/261138718469_ada58732a18da119678d_36.png",
-            "image_48": "https://slack-files2.s3-us-west-2.amazonaws.com/avatars/2017-10-24/261138718469_ada58732a18da119678d_48.png",
-            "image_72": "https://slack-files2.s3-us-west-2.amazonaws.com/avatars/2017-10-24/261138718469_ada58732a18da119678d_72.png"
-        },
-        "team_id": "T01A9CUMPQA"
-    },
-    "blocks": [],
-    "channel": "C01AZ844Z32",
-    "event_ts": "1358877455.000010",
-    "ts": "1358877455.000010"
+    "icons": {}
 }`
 
 func TestBotMessage(t *testing.T) {
@@ -298,17 +280,6 @@ func TestBotMessage(t *testing.T) {
 	assert.NotNil(t, message.Icons)
 	assert.Empty(t, message.Icons.IconURL)
 	assert.Empty(t, message.Icons.IconEmoji)
-	assert.Equal(t, "github", message.BotProfile.Name)
-	assert.Equal(t, "BB12033", message.BotProfile.ID)
-	assert.Equal(t, false, message.BotProfile.Deleted)
-	assert.Equal(t, int64(1599574335), message.BotProfile.Updated)
-	assert.Equal(t, "A6DB2SWUW", message.BotProfile.AppID)
-	assert.Equal(t, "https://slack-files2.s3-us-west-2.amazonaws.com/avatars/2017-10-24/261138718469_ada58732a18da119678d_36.png", message.BotProfile.Icons.Image36)
-	assert.Equal(t, "https://slack-files2.s3-us-west-2.amazonaws.com/avatars/2017-10-24/261138718469_ada58732a18da119678d_48.png", message.BotProfile.Icons.Image48)
-	assert.Equal(t, "https://slack-files2.s3-us-west-2.amazonaws.com/avatars/2017-10-24/261138718469_ada58732a18da119678d_72.png", message.BotProfile.Icons.Image72)
-	assert.Equal(t, "T01A9CUMPQA", message.BotProfile.TeamID)
-	assert.Equal(t, "C01AZ844Z32", message.Channel)
-	assert.Equal(t, "1358877455.000010", message.EventTimestamp)
 }
 
 var meMessage = `{
diff --git a/slackevents/inner_events_test.go b/slackevents/inner_events_test.go
index 10cca25..ff99efb 100644
--- a/slackevents/inner_events_test.go
+++ b/slackevents/inner_events_test.go
@@ -152,54 +152,6 @@ func TestBotMessageEvent(t *testing.T) {
 	}
 }
 
-func TestThreadBroadcastEvent(t *testing.T) {
-	rawE := []byte(`
-			{
-				"type": "message",
-				"subtype": "thread_broadcast",
-				"channel": "G024BE91L",
-				"user": "U2147483697",
-				"text": "Live long and prospect.",
-				"ts": "1355517523.000005",
-				"event_ts": "1355517523.000005",
-				"channel_type": "channel",
-				"source_team": "T3MQV36V7",
-				"user_team": "T3MQV36V7",
-				"message": {
-					"text": "To infinity and beyond.",
-					"root": {
-						"text": "To infinity and beyond.",
-						"ts": "1355517523.000005"
-					},
-					"edited": {
-						"user": "U2147483697",
-						"ts": "1355517524.000000"
-					}
-				},
-				"previous_message": {
-					"text": "Live long and prospect."
-				}
-		}
-	`)
-
-	var me MessageEvent
-	if err := json.Unmarshal(rawE, &me); err != nil {
-		t.Error(err)
-	}
-
-	if me.Root != nil {
-		t.Error("me.Root should be nil")
-	}
-
-	if me.Message.Root == nil {
-		t.Fatal("me.Message.Root is nil")
-	}
-
-	if me.Message.Root.TimeStamp != "1355517523.000005" {
-		t.Errorf("me.Message.Root.TimeStamp = %q, want %q", me.Root.TimeStamp, "1355517523.000005")
-	}
-}
-
 func TestMemberJoinedChannelEvent(t *testing.T) {
 	rawE := []byte(`
 			{
diff --git a/slacktest/data.go b/slacktest/data.go
index 37d413a..a1413b9 100644
--- a/slacktest/data.go
+++ b/slacktest/data.go
@@ -13,7 +13,6 @@ const defaultNonBotUserID = "W012A3CDE"
 const defaultNonBotUserName = "Egon Spengler"
 const defaultTeamName = "SlackTest Team"
 const defaultTeamDomain = "testdomain"
-const defaultConversationName = "endeavor"
 
 var defaultCreatedTs = nowAsJSONTime()
 
@@ -188,59 +187,3 @@ var defaultNonBotUser = fmt.Sprintf(`
 			"locale": "en-US"
 		}
 	`, defaultNonBotUserID, defaultTeamID, defaultNonBotUserName, defaultNonBotUserName, defaultNonBotUserName, defaultTeamID)
-
-var templateConversationJSON = `
-{
-	"ok": true,
-	"channel": {
-		"id": "C0EAQDV4Z",
-		"name": "%s",
-		"is_channel": true,
-		"is_group": false,
-		"is_im": false,
-		"created": %d,
-		"creator": "%s",
-		"is_archived": false,
-		"is_general": false,
-		"unlinked": 0,
-		"name_normalized": "%s",
-		"is_shared": false,
-		"is_ext_shared": false,
-		"is_org_shared": false,
-		"pending_shared": [],
-		"is_pending_ext_shared": false,
-		"is_member": true,
-		"is_private": false,
-		"is_mpim": false,
-		"last_read": "0000000000.000000",
-		"latest": null,
-		"unread_count": 0,
-		"unread_count_display": 0,
-		"topic": {
-			"value": "%s",
-			"creator": "%s",
-			"last_set": %d
-		},
-		"purpose": {
-			"value": "%s",
-			"creator": "%s",
-			"last_set": %d
-		},
-        "num_members": %d,
-		"previous_names": [],
-		"priority": 0
-	}
-}`
-
-var defaultConversationJSON = fmt.Sprintf(templateConversationJSON, defaultConversationName,
-	nowAsJSONTime(), defaultBotID, defaultConversationName, "", "", 0, "", "", 0, 0)
-
-var conversionPurposeTopicJSON = fmt.Sprintf(templateConversationJSON, defaultConversationName,
-	nowAsJSONTime(), defaultBotID, defaultConversationName, "Apply topically for best effects", defaultBotID,
-	nowAsJSONTime(), "I didn't set this purpose on purpose!", defaultBotID, nowAsJSONTime(), 0)
-
-var renameConversationJSON = fmt.Sprintf(templateConversationJSON, "newName",
-	nowAsJSONTime, defaultBotID, "newName", "", "", 0, "", "", 0, 0)
-
-var inviteConversationJSON = fmt.Sprintf(templateConversationJSON, defaultConversationName,
-	nowAsJSONTime(), defaultBotID, defaultConversationName, "", "", 0, "", "", 0, 1)
diff --git a/slacktest/handlers.go b/slacktest/handlers.go
index ebf10c0..e13b15b 100644
--- a/slacktest/handlers.go
+++ b/slacktest/handlers.go
@@ -39,79 +39,11 @@ func botsInfoHandler(w http.ResponseWriter, r *http.Request) {
 	_, _ = w.Write([]byte(defaultBotInfoJSON(r.Context())))
 }
 
-type GroupConversationResponse struct {
-	Ok      bool                    `json:"ok"`
-	Channel slack.GroupConversation `json:"channel"`
-}
-
-func (sts *Server) conversationsInfoHandler(w http.ResponseWriter, r *http.Request) {
-	data, err := ioutil.ReadAll(r.Body)
-	if err != nil {
-		msg := fmt.Sprintf("error reading body: %s", err.Error())
-		log.Printf(msg)
-		http.Error(w, msg, http.StatusInternalServerError)
-		return
-	}
-	values, vErr := url.ParseQuery(string(data))
-	if vErr != nil {
-		msg := fmt.Sprintf("Unable to decode query params: %s", vErr.Error())
-		log.Printf(msg)
-		http.Error(w, msg, http.StatusInternalServerError)
-		return
-	}
-	ch := values.Get("channel")
-
-	response := GroupConversationResponse{
-		Ok:      true,
-		Channel: slack.GroupConversation{
-			Conversation: slack.Conversation{
-				ID: ch,
-			},
-			// Since we don't join channels by name, only ID, let's strip the C prefix and use that as the name.
-			Name: ch[1:],
-		},
-	}
-	encoded, err := json.Marshal(&response)
-	if vErr != nil {
-		msg := fmt.Sprintf("Unable to encode response: %s", vErr.Error())
-		log.Printf(msg)
-		http.Error(w, msg, http.StatusInternalServerError)
-		return
-	}
-
-	_, _ = w.Write(encoded)
-}
-
 // handle channels.list
 func listChannelsHandler(w http.ResponseWriter, _ *http.Request) {
 	_, _ = w.Write([]byte(defaultChannelsListJSON))
 }
 
-// handle conversations.create
-func createConversationHandler(w http.ResponseWriter, r *http.Request) {
-	_, _ = w.Write([]byte(defaultConversationJSON))
-}
-
-// handle conversations.setTopic
-func setConversationTopicHandler(w http.ResponseWriter, r *http.Request) {
-	_, _ = w.Write([]byte(conversionPurposeTopicJSON))
-}
-
-// handle conversations.setPurpose
-func setConversationPurposeHandler(w http.ResponseWriter, r *http.Request) {
-	_, _ = w.Write([]byte(conversionPurposeTopicJSON))
-}
-
-// handle conversations.rename
-func renameConversationHandler(w http.ResponseWriter, r *http.Request) {
-	_, _ = w.Write([]byte(renameConversationJSON))
-}
-
-// handle conversations.invite
-func inviteConversationHandler(w http.ResponseWriter, r *http.Request) {
-	_, _ = w.Write([]byte(inviteConversationJSON))
-}
-
 // handle groups.list
 func listGroupsHandler(w http.ResponseWriter, _ *http.Request) {
 	_, _ = w.Write([]byte(defaultGroupsListJSON))
@@ -168,25 +100,6 @@ func (sts *Server) postMessageHandler(w http.ResponseWriter, r *http.Request) {
 		}
 		m.Attachments = attaches
 	}
-	blocks := values.Get("blocks")
-	if blocks != "" {
-		decoded, err := url.QueryUnescape(blocks)
-		if err != nil {
-			msg := fmt.Sprintf("Unable to decode blocks: %s", err.Error())
-			log.Printf(msg)
-			http.Error(w, msg, http.StatusInternalServerError)
-			return
-		}
-		var decodedBlocks slack.Blocks
-		dbJErr := json.Unmarshal([]byte(decoded), &decodedBlocks)
-		if dbJErr != nil {
-			msg := fmt.Sprintf("Unable to decode blocks string to json: %s", dbJErr.Error())
-			log.Printf(msg)
-			http.Error(w, msg, http.StatusInternalServerError)
-			return
-		}
-		m.Blocks = decodedBlocks
-	}
 	jsonMessage, jsonErr := json.Marshal(m)
 	if jsonErr != nil {
 		msg := fmt.Sprintf("Unable to marshal message: %s", jsonErr.Error())
diff --git a/slacktest/handlers_test.go b/slacktest/handlers_test.go
index b3ef5fe..6d2ea8a 100644
--- a/slacktest/handlers_test.go
+++ b/slacktest/handlers_test.go
@@ -46,62 +46,6 @@ func TestServerListChannels(t *testing.T) {
 	}
 }
 
-func TestServerCreateConversationHandler(t *testing.T) {
-	s := NewTestServer()
-	go s.Start()
-
-	client := slack.New("ABCDEFG", slack.OptionAPIURL(s.GetAPIURL()))
-	conversation, err := client.CreateConversation("test", false)
-	assert.NoError(t, err)
-	assert.Equal(t, "C0EAQDV4Z", conversation.ID)
-	assert.Equal(t, "U023BECGF", conversation.Creator)
-	assert.Equal(t, "endeavor", conversation.Name)
-}
-
-func TestServerSetConversationTopicHandler(t *testing.T) {
-	s := NewTestServer()
-	go s.Start()
-
-	client := slack.New("ABCDEFG", slack.OptionAPIURL(s.GetAPIURL()))
-	response, err := client.SetTopicOfConversation("test", "Apply topically for best effects")
-	assert.NoError(t, err)
-	assert.Equal(t, "Apply topically for best effects", response.Topic.Value)
-	assert.Equal(t, "U023BECGF", response.Topic.Creator)
-	assert.NotEmpty(t, response.Topic.LastSet)
-}
-
-func TestServerSetConversationPurposeHandler(t *testing.T) {
-	s := NewTestServer()
-	go s.Start()
-
-	client := slack.New("ABCDEFG", slack.OptionAPIURL(s.GetAPIURL()))
-	response, err := client.SetPurposeOfConversation("test", "I didn't set this purpose on purpose!")
-	assert.NoError(t, err)
-	assert.Equal(t, "I didn't set this purpose on purpose!", response.Purpose.Value)
-	assert.Equal(t, "U023BECGF", response.Purpose.Creator)
-	assert.NotEmpty(t, response.Purpose.LastSet)
-}
-
-func TestServerRenameConversationHandler(t *testing.T) {
-	s := NewTestServer()
-	go s.Start()
-
-	client := slack.New("ABCDEFG", slack.OptionAPIURL(s.GetAPIURL()))
-	response, err := client.RenameConversation("ID", "newName")
-	assert.NoError(t, err)
-	assert.Equal(t, "newName", response.Name)
-}
-
-func TestServerInviteConversationHandler(t *testing.T) {
-	s := NewTestServer()
-	go s.Start()
-
-	client := slack.New("ABCDEFG", slack.OptionAPIURL(s.GetAPIURL()))
-	response, err := client.InviteUsersToConversation("conversationID", "username")
-	assert.NoError(t, err)
-	assert.Equal(t, 1, response.NumMembers)
-}
-
 func TestUserInfoHandler(t *testing.T) {
 	s := NewTestServer()
 	go s.Start()
@@ -114,18 +58,6 @@ func TestUserInfoHandler(t *testing.T) {
 	assert.True(t, user.IsAdmin)
 }
 
-func TestUserLookUpByEmailHandler(t *testing.T) {
-	s := NewTestServer()
-	go s.Start()
-
-	client := slack.New("ABCDEFG", slack.OptionAPIURL(s.GetAPIURL()))
-	user, err := client.GetUserByEmail("user@email.com")
-	assert.NoError(t, err)
-	assert.Equal(t, "W012A3CDE", user.ID)
-	assert.Equal(t, "spengler", user.Name)
-	assert.True(t, user.IsAdmin)
-}
-
 func TestBotInfoHandler(t *testing.T) {
 	s := NewTestServer()
 	go s.Start()
diff --git a/slacktest/server.go b/slacktest/server.go
index 8187ac6..9cae606 100644
--- a/slacktest/server.go
+++ b/slacktest/server.go
@@ -45,20 +45,13 @@ func NewTestServer(custom ...binder) *Server {
 		c(s)
 	}
 
-	s.Handle("/conversations.info", s.conversationsInfoHandler)
 	s.Handle("/ws", s.wsHandler)
 	s.Handle("/rtm.start", rtmStartHandler)
 	s.Handle("/rtm.connect", RTMConnectHandler)
 	s.Handle("/chat.postMessage", s.postMessageHandler)
 	s.Handle("/channels.list", listChannelsHandler)
-	s.Handle("/conversations.create", createConversationHandler)
-	s.Handle("/conversations.setTopic", setConversationTopicHandler)
-	s.Handle("/conversations.setPurpose", setConversationPurposeHandler)
-	s.Handle("/conversations.rename", renameConversationHandler)
-	s.Handle("/conversations.invite", inviteConversationHandler)
 	s.Handle("/groups.list", listGroupsHandler)
 	s.Handle("/users.info", usersInfoHandler)
-	s.Handle("/users.lookupByEmail", usersInfoHandler)
 	s.Handle("/bots.info", botsInfoHandler)
 	s.Handle("/auth.test", authTestHandler)
 
diff --git a/views_test.go b/views_test.go
index 97f3907..20afa9c 100644
--- a/views_test.go
+++ b/views_test.go
@@ -3,6 +3,7 @@ package slack
 import (
 	"encoding/json"
 	"net/http"
+	"reflect"
 	"testing"
 
 	"github.com/slack-go/slack/internal/errorsx"
@@ -25,50 +26,31 @@ func TestSlack_OpenView(t *testing.T) {
 	api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
 
 	cases := []struct {
-		caseName         string
-		triggerID        string
-		modalViewRequest ModalViewRequest
-		rawResp          string
-		expectedResp     *ViewResponse
-		expectedErr      error
+		caseName     string
+		triggerID    string
+		rawResp      string
+		expectedResp *ViewResponse
+		expectedErr  error
 	}{
 		{
-			caseName:         "pass empty trigger_id",
-			triggerID:        "",
-			modalViewRequest: ModalViewRequest{},
-			rawResp:          "",
-			expectedResp:     nil,
-			expectedErr:      ErrParametersMissing,
-		},
-		{
-			caseName:  "raise an error for not having a unique block id",
-			triggerID: "dummy_trigger_id",
-			modalViewRequest: ModalViewRequest{
-				Blocks: Blocks{
-					BlockSet: []Block{
-						&InputBlock{
-							BlockID: "example",
-						},
-						&InputBlock{
-							BlockID: "example",
-						},
-					},
-				},
-			},
+			caseName:     "pass empty trigger_id",
+			triggerID:    "",
 			rawResp:      "",
 			expectedResp: nil,
-			expectedErr:  ErrBlockIDNotUnique,
+			expectedErr:  ErrParametersMissing,
 		},
 		{
-			caseName:         "raise an error from Slack API",
-			triggerID:        "dummy_trigger_id",
-			modalViewRequest: ModalViewRequest{},
+			caseName:  "raise an error from Slack API",
+			triggerID: "dummy_trigger_id",
 			rawResp: `{
 				"ok": false,
 				"error": "dummy_error_from_slack",
 				"response_metadata": {
+					"warnings": [
+						"missing_charset"
+					],
 					"messages": [
-						"dummy error response"
+						"[WARN] A Content-Type HTTP header was presented but did not declare a charset, such as a 'utf-8'"
 					]
 				}
 			}`,
@@ -76,15 +58,18 @@ func TestSlack_OpenView(t *testing.T) {
 				SlackResponse{
 					Ok:    false,
 					Error: dummySlackErr.Error(),
+					ResponseMetadata: ResponseMetadata{
+						Messages: []string{"[WARN] A Content-Type HTTP header was presented but did not declare a charset, such as a 'utf-8'"},
+						Warnings: []string{"missing_charset"},
+					},
 				},
 				View{},
 			},
 			expectedErr: dummySlackErr,
 		},
 		{
-			caseName:         "success",
-			triggerID:        "dummy_trigger_id",
-			modalViewRequest: ModalViewRequest{},
+			caseName:  "success",
+			triggerID: "dummy_trigger_id",
 			rawResp: `{
 				"ok": true,
 				"view": {
@@ -101,16 +86,19 @@ func TestSlack_OpenView(t *testing.T) {
 					},
 					"blocks": [
 						{
-							"type": "rich_text",
+							"type": "input",
 							"block_id": "a_block_id",
 							"label": {
 								"type": "plain_text",
-								"text": "A simple label",
-								"emoji": true
+								"text": "A simple label"
 							},
 							"optional": false,
 							"element": {
 								"type": "plain_text_input",
+								"placeholder": {
+									"type": "plain_text",
+									"text": "Placeholder text"
+						 		},
 								"action_id": "an_action_id"
 							}
 						}
@@ -135,8 +123,41 @@ func TestSlack_OpenView(t *testing.T) {
 					Error: "",
 				},
 				View{
-					ID:   "VMHU10V25",
-					Type: VTModal,
+					ID:     "VMHU10V25",
+					Type:   VTModal,
+					TeamID: "T8N4K1JN",
+					Title: &TextBlockObject{
+						Type: PlainTextType,
+						Text: "Quite a plain modal",
+					},
+					Submit: &TextBlockObject{
+						Type: PlainTextType,
+						Text: "Create",
+					},
+					CallbackID:      "identify_your_modals",
+					PrivateMetadata: "Shh it is a secret",
+					State:           &ViewState{},
+					Hash:            "156772938.1827394",
+					RootViewID:      "VMHU10V25",
+					AppID:           "AA4928AQ",
+					BotID:           "BA13894H",
+					Blocks: Blocks{
+						BlockSet: []Block{
+							NewInputBlock(
+								"a_block_id",
+								&TextBlockObject{
+									Type: PlainTextType,
+									Text: "A simple label",
+								},
+								NewPlainTextInputBlockElement(
+									&TextBlockObject{
+										Type: PlainTextType,
+										Text: "Placeholder text",
+									},
+									"an_action_id",
+								)),
+						},
+					},
 				},
 			},
 			expectedErr: nil,
@@ -149,24 +170,24 @@ func TestSlack_OpenView(t *testing.T) {
 		t.Run(c.caseName, func(t *testing.T) {
 			h.rawResponse = c.rawResp
 
-			resp, err := api.OpenView(c.triggerID, c.modalViewRequest)
+			resp, err := api.OpenView(c.triggerID, ModalViewRequest{})
 			if c.expectedErr == nil && err != nil {
-				t.Errorf("unexpected error: %s\n", err)
-				return
+				t.Fatalf("unexpected error: %s\n", err)
 			}
 			if c.expectedErr != nil && err == nil {
-				t.Errorf("expected %s, but did not raise an error", c.expectedErr)
-				return
+				t.Fatalf("expected %s, but did not raise an error", c.expectedErr)
 			}
 			if c.expectedErr != nil && err != nil && c.expectedErr.Error() != err.Error() {
-				t.Errorf("expected %s as error but got %s\n", c.expectedErr, err)
-				return
+				t.Fatalf("expected %s as error but got %s\n", c.expectedErr, err)
 			}
 			if resp == nil || c.expectedResp == nil {
 				return
 			}
-			if c.expectedResp.ID != resp.ID || c.expectedResp.Type != resp.Type {
-				t.Errorf("expected:\n\t%v\nas response but got:\n\t%v\n", c.expectedResp, resp)
+			if !reflect.DeepEqual(resp.ResponseMetadata.Messages, c.expectedResp.ResponseMetadata.Messages) {
+				t.Fatalf("expected:\n\t%v\n but got:\n\t%v\n", c.expectedResp.ResponseMetadata.Messages, resp.ResponseMetadata.Messages)
+			}
+			if !reflect.DeepEqual(resp.ResponseMetadata.Warnings, c.expectedResp.ResponseMetadata.Warnings) {
+				t.Fatalf("expected:\n\t%v\n but got:\n\t%v\n", c.expectedResp.ResponseMetadata.Warnings, resp.ResponseMetadata.Warnings)
 			}
 		})
 	}
@@ -197,8 +218,11 @@ func TestSlack_View_PublishView(t *testing.T) {
 				"ok": false,
 				"error": "dummy_error_from_slack",
 				"response_metadata": {
+					"warnings": [
+						"missing_charset"
+					],
 					"messages": [
-						"dummy error response"
+						"[WARN] A Content-Type HTTP header was presented but did not declare a charset, such as a 'utf-8'"
 					]
 				}
 			}`,
@@ -206,6 +230,10 @@ func TestSlack_View_PublishView(t *testing.T) {
 				SlackResponse{
 					Ok:    false,
 					Error: dummySlackErr.Error(),
+					ResponseMetadata: ResponseMetadata{
+						Messages: []string{"[WARN] A Content-Type HTTP header was presented but did not declare a charset, such as a 'utf-8'"},
+						Warnings: []string{"missing_charset"},
+					},
 				},
 				View{},
 			},
@@ -224,12 +252,20 @@ func TestSlack_View_PublishView(t *testing.T) {
 					"submit": null,
 					"blocks": [
 						{
-							"type": "section",
-							"block_id": "2WGp9",
-							"text": {
-								"type": "mrkdwn",
-								"text": "A simple section with some sample sentence.",
-								"verbatim": false
+							"type": "input",
+							"block_id": "a_block_id",
+							"label": {
+								"type": "plain_text",
+								"text": "A simple label"
+							},
+							"optional": false,
+							"element": {
+								"type": "plain_text_input",
+								"placeholder": {
+									"type": "plain_text",
+									"text": "Placeholder text"
+						 		},
+								"action_id": "an_action_id"
 							}
 						}
 					],
@@ -254,8 +290,33 @@ func TestSlack_View_PublishView(t *testing.T) {
 					Error: "",
 				},
 				View{
-					ID:   "VMHU10V25",
-					Type: VTHomeTab,
+					ID:              "VMHU10V25",
+					Type:            VTHomeTab,
+					TeamID:          "T8N4K1JN",
+					CallbackID:      "identify_your_home_tab",
+					PrivateMetadata: "Shh it is a secret",
+					State:           &ViewState{},
+					Hash:            "156772938.1827394",
+					RootViewID:      "VMHU10V25",
+					AppID:           "AA4928AQ",
+					BotID:           "BA13894H",
+					Blocks: Blocks{
+						BlockSet: []Block{
+							NewInputBlock(
+								"a_block_id",
+								&TextBlockObject{
+									Type: PlainTextType,
+									Text: "A simple label",
+								},
+								NewPlainTextInputBlockElement(
+									&TextBlockObject{
+										Type: PlainTextType,
+										Text: "Placeholder text",
+									},
+									"an_action_id",
+								)),
+						},
+					},
 				},
 			},
 			expectedErr: nil,
@@ -270,22 +331,22 @@ func TestSlack_View_PublishView(t *testing.T) {
 
 			resp, err := api.PublishView(c.userID, HomeTabViewRequest{}, "dummy_hash")
 			if c.expectedErr == nil && err != nil {
-				t.Errorf("unexpected error: %s\n", err)
-				return
+				t.Fatalf("unexpected error: %s\n", err)
 			}
 			if c.expectedErr != nil && err == nil {
-				t.Errorf("expected %s, but did not raise an error", c.expectedErr)
-				return
+				t.Fatalf("expected %s, but did not raise an error", c.expectedErr)
 			}
 			if c.expectedErr != nil && err != nil && c.expectedErr.Error() != err.Error() {
-				t.Errorf("expected %s as error but got %s\n", c.expectedErr, err)
-				return
+				t.Fatalf("expected %s as error but got %s\n", c.expectedErr, err)
 			}
 			if resp == nil || c.expectedResp == nil {
 				return
 			}
-			if c.expectedResp.ID != resp.ID || c.expectedResp.Type != resp.Type {
-				t.Errorf("expected:\n\t%v\nas response but got:\n\t%v\n", c.expectedResp, resp)
+			if !reflect.DeepEqual(resp.ResponseMetadata.Messages, c.expectedResp.ResponseMetadata.Messages) {
+				t.Fatalf("expected:\n\t%v\n but got:\n\t%v\n", c.expectedResp.ResponseMetadata.Messages, resp.ResponseMetadata.Messages)
+			}
+			if !reflect.DeepEqual(resp.ResponseMetadata.Warnings, c.expectedResp.ResponseMetadata.Warnings) {
+				t.Fatalf("expected:\n\t%v\n but got:\n\t%v\n", c.expectedResp.ResponseMetadata.Warnings, resp.ResponseMetadata.Warnings)
 			}
 		})
 	}
@@ -316,8 +377,11 @@ func TestSlack_PushView(t *testing.T) {
 				"ok": false,
 				"error": "dummy_error_from_slack",
 				"response_metadata": {
+					"warnings": [
+						"missing_charset"
+					],
 					"messages": [
-						"dummy error response"
+						"[WARN] A Content-Type HTTP header was presented but did not declare a charset, such as a 'utf-8'"
 					]
 				}
 			}`,
@@ -325,6 +389,10 @@ func TestSlack_PushView(t *testing.T) {
 				SlackResponse{
 					Ok:    false,
 					Error: dummySlackErr.Error(),
+					ResponseMetadata: ResponseMetadata{
+						Messages: []string{"[WARN] A Content-Type HTTP header was presented but did not declare a charset, such as a 'utf-8'"},
+						Warnings: []string{"missing_charset"},
+					},
 				},
 				View{},
 			},
@@ -349,16 +417,19 @@ func TestSlack_PushView(t *testing.T) {
 					},
 					"blocks": [
 						{
-							"type": "rich_text",
+							"type": "input",
 							"block_id": "a_block_id",
 							"label": {
 								"type": "plain_text",
-								"text": "A simple label",
-								"emoji": true
+								"text": "A simple label"
 							},
 							"optional": false,
 							"element": {
 								"type": "plain_text_input",
+								"placeholder": {
+									"type": "plain_text",
+									"text": "Placeholder text"
+						 		},
 								"action_id": "an_action_id"
 							}
 						}
@@ -383,8 +454,41 @@ func TestSlack_PushView(t *testing.T) {
 					Error: "",
 				},
 				View{
-					ID:   "VMHU10V25",
-					Type: VTModal,
+					ID:     "VMHU10V25",
+					Type:   VTModal,
+					TeamID: "T8N4K1JN",
+					Title: &TextBlockObject{
+						Type: PlainTextType,
+						Text: "Quite a plain modal",
+					},
+					Submit: &TextBlockObject{
+						Type: PlainTextType,
+						Text: "Create",
+					},
+					CallbackID:      "identify_your_modals",
+					PrivateMetadata: "Shh it is a secret",
+					State:           &ViewState{},
+					Hash:            "156772938.1827394",
+					RootViewID:      "VMHU10V25",
+					AppID:           "AA4928AQ",
+					BotID:           "BA13894H",
+					Blocks: Blocks{
+						BlockSet: []Block{
+							NewInputBlock(
+								"a_block_id",
+								&TextBlockObject{
+									Type: PlainTextType,
+									Text: "A simple label",
+								},
+								NewPlainTextInputBlockElement(
+									&TextBlockObject{
+										Type: PlainTextType,
+										Text: "Placeholder text",
+									},
+									"an_action_id",
+								)),
+						},
+					},
 				},
 			},
 			expectedErr: nil,
@@ -399,22 +503,22 @@ func TestSlack_PushView(t *testing.T) {
 
 			resp, err := api.PushView(c.triggerID, ModalViewRequest{})
 			if c.expectedErr == nil && err != nil {
-				t.Errorf("unexpected error: %s\n", err)
-				return
+				t.Fatalf("unexpected error: %s\n", err)
 			}
 			if c.expectedErr != nil && err == nil {
-				t.Errorf("expected %s, but did not raise an error", c.expectedErr)
-				return
+				t.Fatalf("expected %s, but did not raise an error", c.expectedErr)
 			}
 			if c.expectedErr != nil && err != nil && c.expectedErr.Error() != err.Error() {
-				t.Errorf("expected %s as error but got %s\n", c.expectedErr, err)
-				return
+				t.Fatalf("expected %s as error but got %s\n", c.expectedErr, err)
 			}
 			if resp == nil || c.expectedResp == nil {
 				return
 			}
-			if c.expectedResp.ID != resp.ID || c.expectedResp.Type != resp.Type {
-				t.Errorf("expected:\n\t%v\nas response but got:\n\t%v\n", c.expectedResp, resp)
+			if !reflect.DeepEqual(resp.ResponseMetadata.Messages, c.expectedResp.ResponseMetadata.Messages) {
+				t.Fatalf("expected:\n\t%v\n but got:\n\t%v\n", c.expectedResp.ResponseMetadata.Messages, resp.ResponseMetadata.Messages)
+			}
+			if !reflect.DeepEqual(resp.ResponseMetadata.Warnings, c.expectedResp.ResponseMetadata.Warnings) {
+				t.Fatalf("expected:\n\t%v\n but got:\n\t%v\n", c.expectedResp.ResponseMetadata.Warnings, resp.ResponseMetadata.Warnings)
 			}
 		})
 	}
@@ -448,8 +552,11 @@ func TestSlack_UpdateView(t *testing.T) {
 				"ok": false,
 				"error": "dummy_error_from_slack",
 				"response_metadata": {
+					"warnings": [
+						"missing_charset"
+					],
 					"messages": [
-						"dummy error response"
+						"[WARN] A Content-Type HTTP header was presented but did not declare a charset, such as a 'utf-8'"
 					]
 				}
 			}`,
@@ -457,6 +564,10 @@ func TestSlack_UpdateView(t *testing.T) {
 				SlackResponse{
 					Ok:    false,
 					Error: dummySlackErr.Error(),
+					ResponseMetadata: ResponseMetadata{
+						Messages: []string{"[WARN] A Content-Type HTTP header was presented but did not declare a charset, such as a 'utf-8'"},
+						Warnings: []string{"missing_charset"},
+					},
 				},
 				View{},
 			},
@@ -482,16 +593,19 @@ func TestSlack_UpdateView(t *testing.T) {
 					},
 					"blocks": [
 						{
-							"type": "rich_text",
+							"type": "input",
 							"block_id": "a_block_id",
 							"label": {
 								"type": "plain_text",
-								"text": "A simple label",
-								"emoji": true
+								"text": "A simple label"
 							},
 							"optional": false,
 							"element": {
 								"type": "plain_text_input",
+								"placeholder": {
+									"type": "plain_text",
+									"text": "Placeholder text"
+						 		},
 								"action_id": "an_action_id"
 							}
 						}
@@ -516,8 +630,41 @@ func TestSlack_UpdateView(t *testing.T) {
 					Error: "",
 				},
 				View{
-					ID:   "VMHU10V25",
-					Type: VTModal,
+					ID:     "VMHU10V25",
+					Type:   VTModal,
+					TeamID: "T8N4K1JN",
+					Title: &TextBlockObject{
+						Type: PlainTextType,
+						Text: "Quite a plain modal",
+					},
+					Submit: &TextBlockObject{
+						Type: PlainTextType,
+						Text: "Create",
+					},
+					CallbackID:      "identify_your_modals",
+					PrivateMetadata: "Shh it is a secret",
+					State:           &ViewState{},
+					Hash:            "156772938.1827394",
+					RootViewID:      "VMHU10V25",
+					AppID:           "AA4928AQ",
+					BotID:           "BA13894H",
+					Blocks: Blocks{
+						BlockSet: []Block{
+							NewInputBlock(
+								"a_block_id",
+								&TextBlockObject{
+									Type: PlainTextType,
+									Text: "A simple label",
+								},
+								NewPlainTextInputBlockElement(
+									&TextBlockObject{
+										Type: PlainTextType,
+										Text: "Placeholder text",
+									},
+									"an_action_id",
+								)),
+						},
+					},
 				},
 			},
 			expectedErr: nil,
@@ -532,22 +679,22 @@ func TestSlack_UpdateView(t *testing.T) {
 
 			resp, err := api.UpdateView(ModalViewRequest{}, c.externalID, "dummy_hash", c.viewID)
 			if c.expectedErr == nil && err != nil {
-				t.Errorf("unexpected error: %s\n", err)
-				return
+				t.Fatalf("unexpected error: %s\n", err)
 			}
 			if c.expectedErr != nil && err == nil {
-				t.Errorf("expected %s, but did not raise an error", c.expectedErr)
-				return
+				t.Fatalf("expected %s, but did not raise an error", c.expectedErr)
 			}
 			if c.expectedErr != nil && err != nil && c.expectedErr.Error() != err.Error() {
-				t.Errorf("expected %s as error but got %s\n", c.expectedErr, err)
-				return
+				t.Fatalf("expected %s as error but got %s\n", c.expectedErr, err)
 			}
 			if resp == nil || c.expectedResp == nil {
 				return
 			}
-			if c.expectedResp.ID != resp.ID || c.expectedResp.Type != resp.Type {
-				t.Errorf("expected:\n\t%v\nas response but got:\n\t%v\n", c.expectedResp, resp)
+			if !reflect.DeepEqual(resp.ResponseMetadata.Messages, c.expectedResp.ResponseMetadata.Messages) {
+				t.Fatalf("expected:\n\t%v\n but got:\n\t%v\n", c.expectedResp.ResponseMetadata.Messages, resp.ResponseMetadata.Messages)
+			}
+			if !reflect.DeepEqual(resp.ResponseMetadata.Warnings, c.expectedResp.ResponseMetadata.Warnings) {
+				t.Fatalf("expected:\n\t%v\n but got:\n\t%v\n", c.expectedResp.ResponseMetadata.Warnings, resp.ResponseMetadata.Warnings)
 			}
 		})
 	}