github.com/mailgun/mailgun-go/v3@v3.6.4/webhooks.go (about) 1 package mailgun 2 3 import ( 4 "context" 5 "crypto/hmac" 6 "crypto/sha256" 7 "crypto/subtle" 8 "encoding/hex" 9 "fmt" 10 "io" 11 "net/http" 12 13 "github.com/mailgun/mailgun-go/v3/events" 14 ) 15 16 type UrlOrUrls struct { 17 Urls []string `json:"urls"` 18 Url string `json:"url"` 19 } 20 21 type WebHooksListResponse struct { 22 Webhooks map[string]UrlOrUrls `json:"webhooks"` 23 } 24 25 type WebHookResponse struct { 26 Webhook UrlOrUrls `json:"webhook"` 27 } 28 29 // ListWebhooks returns the complete set of webhooks configured for your domain. 30 // Note that a zero-length mapping is not an error. 31 func (mg *MailgunImpl) ListWebhooks(ctx context.Context) (map[string][]string, error) { 32 r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint)) 33 r.setClient(mg.Client()) 34 r.setBasicAuth(basicAuthUser, mg.APIKey()) 35 36 var body WebHooksListResponse 37 err := getResponseFromJSON(ctx, r, &body) 38 if err != nil { 39 return nil, err 40 } 41 42 hooks := make(map[string][]string, 0) 43 for k, v := range body.Webhooks { 44 if v.Url != "" { 45 hooks[k] = []string{v.Url} 46 } 47 if len(v.Urls) != 0 { 48 hooks[k] = append(hooks[k], v.Urls...) 49 } 50 } 51 return hooks, nil 52 } 53 54 // CreateWebhook installs a new webhook for your domain. 55 func (mg *MailgunImpl) CreateWebhook(ctx context.Context, kind string, urls []string) error { 56 r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint)) 57 r.setClient(mg.Client()) 58 r.setBasicAuth(basicAuthUser, mg.APIKey()) 59 p := newUrlEncodedPayload() 60 p.addValue("id", kind) 61 for _, url := range urls { 62 p.addValue("url", url) 63 } 64 _, err := makePostRequest(ctx, r, p) 65 return err 66 } 67 68 // DeleteWebhook removes the specified webhook from your domain's configuration. 69 func (mg *MailgunImpl) DeleteWebhook(ctx context.Context, kind string) error { 70 r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind) 71 r.setClient(mg.Client()) 72 r.setBasicAuth(basicAuthUser, mg.APIKey()) 73 _, err := makeDeleteRequest(ctx, r) 74 return err 75 } 76 77 // GetWebhook retrieves the currently assigned webhook URL associated with the provided type of webhook. 78 func (mg *MailgunImpl) GetWebhook(ctx context.Context, kind string) ([]string, error) { 79 r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind) 80 r.setClient(mg.Client()) 81 r.setBasicAuth(basicAuthUser, mg.APIKey()) 82 var body WebHookResponse 83 if err := getResponseFromJSON(ctx, r, &body); err != nil { 84 return nil, err 85 } 86 87 if body.Webhook.Url != "" { 88 return []string{body.Webhook.Url}, nil 89 } 90 if len(body.Webhook.Urls) != 0 { 91 return body.Webhook.Urls, nil 92 } 93 return nil, fmt.Errorf("webhook '%s' returned no urls", kind) 94 } 95 96 // UpdateWebhook replaces one webhook setting for another. 97 func (mg *MailgunImpl) UpdateWebhook(ctx context.Context, kind string, urls []string) error { 98 r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind) 99 r.setClient(mg.Client()) 100 r.setBasicAuth(basicAuthUser, mg.APIKey()) 101 p := newUrlEncodedPayload() 102 for _, url := range urls { 103 p.addValue("url", url) 104 } 105 _, err := makePutRequest(ctx, r, p) 106 return err 107 } 108 109 // Represents the signature portion of the webhook POST body 110 type Signature struct { 111 TimeStamp string `json:"timestamp"` 112 Token string `json:"token"` 113 Signature string `json:"signature"` 114 } 115 116 // Represents the JSON payload provided when a Webhook is called by mailgun 117 type WebhookPayload struct { 118 Signature Signature `json:"signature"` 119 EventData events.RawJSON `json:"event-data"` 120 } 121 122 // Use this method to parse the webhook signature given as JSON in the webhook response 123 func (mg *MailgunImpl) VerifyWebhookSignature(sig Signature) (verified bool, err error) { 124 h := hmac.New(sha256.New, []byte(mg.APIKey())) 125 io.WriteString(h, sig.TimeStamp) 126 io.WriteString(h, sig.Token) 127 128 calculatedSignature := h.Sum(nil) 129 signature, err := hex.DecodeString(sig.Signature) 130 if err != nil { 131 return false, err 132 } 133 if len(calculatedSignature) != len(signature) { 134 return false, nil 135 } 136 137 return subtle.ConstantTimeCompare(signature, calculatedSignature) == 1, nil 138 } 139 140 // Deprecated: Please use the VerifyWebhookSignature() to parse the latest 141 // version of WebHooks from mailgun 142 func (mg *MailgunImpl) VerifyWebhookRequest(req *http.Request) (verified bool, err error) { 143 h := hmac.New(sha256.New, []byte(mg.APIKey())) 144 io.WriteString(h, req.FormValue("timestamp")) 145 io.WriteString(h, req.FormValue("token")) 146 147 calculatedSignature := h.Sum(nil) 148 signature, err := hex.DecodeString(req.FormValue("signature")) 149 if err != nil { 150 return false, err 151 } 152 if len(calculatedSignature) != len(signature) { 153 return false, nil 154 } 155 156 return subtle.ConstantTimeCompare(signature, calculatedSignature) == 1, nil 157 }