-
Notifications
You must be signed in to change notification settings - Fork 17
/
webhook.go
146 lines (124 loc) · 5.99 KB
/
webhook.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package easypost
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"golang.org/x/text/unicode/norm"
"net/http"
)
// A Webhook represents an EasyPost webhook callback URL.
type Webhook struct {
ID string `json:"id,omitempty" url:"id,omitempty"`
Object string `json:"object,omitempty" url:"object,omitempty"`
Mode string `json:"mode,omitempty" url:"mode,omitempty"`
URL string `json:"url,omitempty" url:"url,omitempty"`
DisabledAt *DateTime `json:"disabled_at,omitempty" url:"disabled_at,omitempty"`
WebhookSecret string `json:"webhook_secret,omitempty" url:"webhook_secret,omitempty"`
}
// CreateUpdateWebhookOptions is used to specify parameters for creating and updating EasyPost webhooks.
type CreateUpdateWebhookOptions struct {
URL string `json:"url,omitempty" url:"url,omitempty"`
WebhookSecret string `json:"webhook_secret,omitempty" url:"webhook_secret,omitempty"`
}
// createUpdateWebhookRequest is the request struct for creating and updating webhooks (internal)
type createUpdateWebhookRequest struct {
Webhook *Webhook `json:"webhook,omitempty" url:"webhook,omitempty"`
}
// listWebhooksResult is the response struct of listing webhooks (internal)
type listWebhooksResult struct {
Webhooks *[]*Webhook `json:"webhooks,omitempty" url:"webhooks,omitempty"`
}
func (c *Client) composeCreateUpdateWebhookRequest(data *CreateUpdateWebhookOptions) *createUpdateWebhookRequest {
return &createUpdateWebhookRequest{
Webhook: &Webhook{
URL: data.URL,
WebhookSecret: data.WebhookSecret,
},
}
}
// CreateWebhookWithDetails creates a new webhook with the provided details.
func (c *Client) CreateWebhookWithDetails(data *CreateUpdateWebhookOptions) (out *Webhook, err error) {
return c.CreateWebhookWithDetailsWithContext(context.Background(), data)
}
// CreateWebhookWithDetailsWithContext performs the same operation as CreateWebhookWithDetails, but
// allows specifying a context that can interrupt the request.
func (c *Client) CreateWebhookWithDetailsWithContext(ctx context.Context,
data *CreateUpdateWebhookOptions) (out *Webhook, err error) {
req := c.composeCreateUpdateWebhookRequest(data)
err = c.do(ctx, http.MethodPost, "webhooks", req, &out)
return
}
// ListWebhooks returns all webhooks associated with the EasyPost account.
func (c *Client) ListWebhooks() (out []*Webhook, err error) {
return c.ListWebhooksWithContext(context.Background())
}
// ListWebhooksWithContext performs the same operation as
// ListWebhooksWithContext, but allows specifying a context that can interrupt
// the request.
func (c *Client) ListWebhooksWithContext(ctx context.Context) (out []*Webhook, err error) {
err = c.do(ctx, http.MethodGet, "webhooks", nil, &listWebhooksResult{Webhooks: &out})
return
}
// GetWebhook retrieves a Webhook object with the given ID.
func (c *Client) GetWebhook(webhookID string) (out *Webhook, err error) {
return c.GetWebhookWithContext(context.Background(), webhookID)
}
// GetWebhookWithContext performs the same operation as GetWebhook, but allows
// specifying a context that can interrupt the request.
func (c *Client) GetWebhookWithContext(ctx context.Context, webhookID string) (out *Webhook, err error) {
err = c.do(ctx, http.MethodGet, "webhooks/"+webhookID, nil, &out)
return
}
// UpdateWebhook updates a webhook. Automatically re-enables webhook if it is disabled.
func (c *Client) UpdateWebhook(webhookID string, data *CreateUpdateWebhookOptions) (out *Webhook, err error) {
return c.UpdateWebhookWithContext(context.Background(), webhookID, data)
}
// UpdateWebhookWithContext performs the same operation as UpdateWebhook, but
// allows specifying a context that can interrupt the request.
func (c *Client) UpdateWebhookWithContext(ctx context.Context,
webhookID string, data *CreateUpdateWebhookOptions) (out *Webhook, err error) {
req := c.composeCreateUpdateWebhookRequest(data)
err = c.do(ctx, http.MethodPatch, "webhooks/"+webhookID, req, &out)
return
}
// DeleteWebhook removes a webhook.
func (c *Client) DeleteWebhook(webhookID string) error {
return c.DeleteWebhookWithContext(context.Background(), webhookID)
}
// DeleteWebhookWithContext performs the same operation as DeleteWebhook, but
// allows specifying a context that can interrupt the request.
func (c *Client) DeleteWebhookWithContext(ctx context.Context, webhookID string) error {
return c.do(ctx, http.MethodDelete, "webhooks/"+webhookID, nil, nil)
}
// ValidateWebhook validates a webhook by comparing the HMAC signature header sent from EasyPost to your shared secret.
// If the signatures do not match, an error will be raised signifying the webhook either did not originate
// from EasyPost or the secrets do not match. If the signatures do match, the `event_body` will be returned as JSON.
func (c *Client) ValidateWebhook(eventBody []byte, headers map[string]interface{}, webhookSecret string) (out *Event, err error) {
return c.ValidateWebhookWithContext(context.Background(), eventBody, headers, webhookSecret)
}
// ValidateWebhookWithContext performs the same operation as ValidateWebhook, but
// allows specifying a context that can interrupt the request.
func (c *Client) ValidateWebhookWithContext(ctx context.Context, eventBody []byte, headers map[string]interface{}, webhookSecret string) (out *Event, err error) {
easypostHmacSignature, signaturePresent := headers["X-Hmac-Signature"].(string)
if signaturePresent {
normalizedSecret := norm.NFKD.String(webhookSecret)
encodedSecret := []byte(normalizedSecret)
expectedSignature := hmac.New(sha256.New, encodedSecret)
expectedSignature.Write(eventBody)
digest := "hmac-sha256-hex=" + hex.EncodeToString(expectedSignature.Sum(nil))
if hmac.Equal([]byte(digest), []byte(easypostHmacSignature)) {
var webhookBody Event
if err = json.Unmarshal(eventBody, &webhookBody); err != nil {
return nil, err
} else {
return &webhookBody, nil
}
} else {
return nil, MismatchWebhookSignatureError
}
} else {
return nil, MissingWebhookSignatureError
}
}