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  }