Developer Tools

Webhooks

Webhook Events are notifications triggered by specific operations to which you can subscribe, allowing you to be alerted when those operations occur. These events might include scenarios such as a Network Token being updated, a successful 3DS session, or the completion of a function deployment. Each event is categorized by a type that defines the nature of the event, along with associated data relevant to that event.

Webhook Event Object

When an event is triggered, Evervault creates a new Event object, which is sent to your Webhook Endpoint as a JSON payload. This payload includes the following fields:

  • id: A unique identifier for the event.
  • type: The type of event that occurred (e.g. payments.network-token.updated or payments.3ds-session.success).
  • data: This field contains the event-specific details.
  • createdAt: An epoch millisecond timestamp marking when the event was generated.

Example Event Object

The following example Event Object is triggered when an update occurs on a Network Token:

1
{
2
"id": "webhook_event_0aa6ff0fee57",
3
"type": "payments.network-token.updated",
4
"data": {
5
"id": "network_token_123456789098",
6
"number": "4242424242424242",
7
"expiry": {
8
"month": "10",
9
"year": "27"
10
},
11
"card": {
12
"lastFour": "6401",
13
"expiry": {
14
"month": "01",
15
"year": "29"
16
}
17
},
18
"paymentAccountReference": "71288989897319237219",
19
"tokenRequestorIdentifier": "40020248564",
20
"tokenServiceProvider": "vts",
21
"merchant": "merchant_a1ef231b7947",
22
"status": "active",
23
"createdAt": 1709293284000,
24
"updatedAt": 1711633194688
25
},
26
"createdAt": 1711633194690
27
}

Webhook Event Delivery

Webhook Events are delivered to a Webhook Endpoint via a POST request over HTTPS. The request includes a Content-Type header set to application/json. A Webhook Event is deemed successfully delivered when the Webhook Endpoint responds with a 2XX HTTP status code.

Single delivery is not guaranteed, and in rare cases, a Webhook Event may be delivered more than once. To handle potential duplicates, you can use the id field in the event payload to identify and filter out duplicate events.

You can register a Webhook Endpoint either through the Evervault dashboard (Settings Section) or via the Evervault API. An Evervault App can have multiple Webhook Endpoints registered, with each endpoint subscribed to a specific set of events.

Retries

If the Webhook Endpoint does not respond with a 2XX status code, Evervault will automatically retry sending the event using an exponential backoff strategy, gradually increasing the delay between each retry. This process continues until the event is successfully delivered or until 5 days (120 hours) have passed. If the event cannot be delivered within this timeframe, it will be permanently discarded.

Monitoring

You can monitor Webhook Event deliveries through the Evervault Dashboard, under the Logs tab. The logs include detailed information about each event, such as timestamps and status codes, enabling you to quickly diagnose and address delivery issues.

Getting Started

1. Deploy a new API Endpoint

To start receiving Webhook Endpoints, you need to set up an API endpoint within your infrastructure that can handle POST HTTP requests and process JSON objects. This endpoint must return a 2XX HTTP status code upon successfully completing the event processing.

1
const express = require('express');
2
const app = express();
3
4
app.use(express.json());
5
6
// Webhooks endpoint
7
app.post('/evervault', (req, res) => {
8
const webhookEvent = req.body;
9
10
switch (webhookEvent.type) {
11
case 'payments.network-token.updated':
12
// Process the Network Token update
13
break;
14
// Handle other event types
15
default:
16
console.log(`Unhandled event type ${event.type}`);
17
}
18
19
// Respond with HTTP status 200
20
res.status(200).send({success: true});
21
});
22
23
// Start the server
24
app.listen(3000, () => console.log('Running on port 3000'));

In this example, we are creating a new endpoint, https://hooks.acme.com/evervault, specifically designed to handle Network Token updates.

2. Secure your Webhook Endpoint

All Webhook Event requests are signed using a secret key, allowing you to verify that the event was sent by Evervault. Each request includes a JSON Web Token (JWT) in the X-Evervault-Signature header, which you can use to authenticate the request. To do this, compare the signature provided in the header with a signature you generate locally using the payload and endpoint URL.

The signature is created by generating a SHA-256 hash of the body of the request. This hash, along with the endpoint URL, is then signed using the ES256 algorithm (ECDSA with a P-256 curve) and a private key. To verify this signature, use the corresponding public key, which you can retrieve from Evervault’s JSON Web Key Set (JWKS) endpoint.

Below are examples of how to verify an Evervault webhook signature using Node.js, Python, Go and Ruby. For optimal performance, it’s recommended to cache the JWKS instead of fetching it with each request.

1
const { createRemoteJWKSet, jwtVerify } = require('jose');
2
const crypto = require('crypto');
3
4
// Function to verify Evervault webhook
5
async function verifyWebhook(req) {
6
try {
7
// Convert the request body to a JSON string
8
const jsonStr = JSON.stringify(req.body);
9
10
// Retrieve the JWT signature from the request headers
11
const jwt = req.headers['X-Evervault-Signature'];
12
13
// Reconstruct the SHA-256 hash of the request body
14
const reconstructedBodySha256 = crypto.createHash('sha256').update(jsonStr).digest('base64');
15
16
// Reconstruct the full endpoint URL
17
const reconstructedEndpointUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
18
19
// Fetch the JWKS (JSON Web Key Set) for webhook verification
20
const JWKS = createRemoteJWKSet(new URL('https://api.evervault.com/jwks/webhooks.json'));
21
22
// Verify the JWT using the JWKS
23
const { payload } = await jwtVerify(jwt, JWKS);
24
const { bodySha256, endpointUrl } = payload;
25
26
// Validate the reconstructed body hash and endpoint URL against the JWT payload
27
if (reconstructedBodySha256 !== bodySha256 || reconstructedEndpointUrl !== endpointUrl) {
28
throw new Error('Body or endpoint URL could not be verified');
29
} else {
30
console.log('Webhook verified!');
31
}
32
} catch (error) {
33
console.error('Webhook verification failed.', error);
34
throw error;
35
}
36
}
37
38
// Example usage of the verifyWebhook function
39
await verifyWebhook(req);
1
import json
2
import hashlib
3
import requests
4
import base64
5
from jwt import decode
6
from jwt.exceptions import InvalidTokenError
7
from cryptography.hazmat.primitives.asymmetric import ec
8
from cryptography.hazmat.primitives import serialization
9
10
def verify_webhook(req):
11
try:
12
# Convert the request body to a JSON string
13
json_str = json.dumps(req.json, separators=(',', ':'))
14
15
# Retrieve the JWT signature from the request headers
16
jwt_token = req.headers.get('X-Evervault-Signature')
17
18
# Reconstruct the SHA-256 hash of the request body
19
reconstructed_body_sha256 = hashlib.sha256(json_str.encode('utf-8')).digest()
20
reconstructed_body_sha256_base64 = base64.b64encode(reconstructed_body_sha256).decode('utf-8')
21
22
# Reconstruct the full endpoint URL
23
reconstructed_endpoint_url = f"{req.scheme}://{req.host}{req.path}".replace('http://', 'https://')
24
25
# Fetch the JWKS (JSON Web Key Set) for webhook verification
26
jwks_url = 'https://api.evervault.io/jwks/webhooks.json'
27
jwks_response = requests.get(jwks_url)
28
jwks = jwks_response.json()
29
30
# Extract the kid from the JWT header
31
unverified_header = get_unverified_header(jwt_token)
32
kid = unverified_header['kid']
33
34
# Find the correct JWK using the kid
35
key = next((k for k in jwks['keys'] if k['kid'] == kid), None)
36
if not key:
37
raise ValueError('Invalid key ID (kid)')
38
39
# Convert JWK to PEM format
40
x = int.from_bytes(base64.urlsafe_b64decode(key['x'] + '=='), 'big')
41
y = int.from_bytes(base64.urlsafe_b64decode(key['y'] + '=='), 'big')
42
public_key = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()).public_key()
43
pem = public_key.public_bytes(
44
encoding=serialization.Encoding.PEM,
45
format=serialization.PublicFormat.SubjectPublicKeyInfo
46
)
47
48
# Verify the JWT using the public key
49
payload = decode(jwt_token, pem, algorithms=['ES256'])
50
body_sha256 = payload['bodySha256']
51
endpoint_url = payload['endpointUrl']
52
53
# Validate the reconstructed body hash and endpoint URL against the JWT payload
54
if reconstructed_body_sha256_base64 != body_sha256 or reconstructed_endpoint_url != endpoint_url:
55
raise ValueError('Body or endpoint URL could not be verified')
56
else:
57
print('Webhook verified!')
58
return True
59
60
except (InvalidTokenError, ValueError) as error:
61
print('Webhook verification failed.', error)
62
return False
63
64
# Example usage of the verify_webhook function
65
if verify_webhook(req):
66
print('Webhook verified!')
67
else:
68
print('Webhook verification failed.')
69
1
import (
2
"crypto/sha256"
3
"encoding/base64"
4
"encoding/json"
5
"fmt"
6
"io/ioutil"
7
"log"
8
"net/http"
9
10
"github.com/MicahParks/keyfunc"
11
"github.com/golang-jwt/jwt/v4"
12
)
13
14
const jwksURL = "https://api.evervault.com/jwks/webhooks.json"
15
16
// WebhookPayload represents the structure of the JWT payload
17
type WebhookPayload struct {
18
BodySha256 string `json:"bodySha256"`
19
EndpointUrl string `json:"endpointUrl"`
20
}
21
22
func verifyWebhook(req *http.Request) (bool, error) {
23
// Read and marshal request body to JSON string
24
body, err := ioutil.ReadAll(req.Body)
25
if err != nil {
26
return false, err
27
}
28
jsonStr, err := json.Marshal(json.RawMessage(body))
29
if err != nil {
30
return false, err
31
}
32
33
// Reconstruct SHA-256 hash of the body
34
hash := sha256.Sum256(jsonStr)
35
reconstructedBodySha256 := base64.StdEncoding.EncodeToString(hash[:])
36
37
// Reconstruct full endpoint URL
38
reconstructedEndpointUrl := fmt.Sprintf("%s://%s%s", "https", req.Host, req.URL.Path)
39
40
// Fetch the JWKS for webhook verification
41
jwks, err := keyfunc.Get(jwksURL, keyfunc.Options{})
42
if err != nil {
43
return false, fmt.Errorf("failed to get JWKS: %w", err)
44
}
45
46
// Retrieve JWT signature from headers
47
jwtToken := req.Header.Get("x-evervault-signature")
48
if jwtToken == "" {
49
return false, fmt.Errorf("missing JWT token")
50
}
51
52
// Decode JWT and verify signature using keys from JWKS
53
token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) {
54
// Use the JWKS to fetch the key for verification
55
return jwks.Keyfunc(token)
56
})
57
if err != nil {
58
return false, fmt.Errorf("JWT verification failed: %w", err)
59
}
60
61
// Check token validity
62
if !token.Valid {
63
return false, fmt.Errorf("JWT token is not valid")
64
}
65
66
// Extract claims from the JWT token
67
claims, ok := token.Claims.(jwt.MapClaims)
68
if !ok {
69
return false, fmt.Errorf("JWT claims are not valid")
70
}
71
72
// Extract values from the claims
73
bodySha256, ok := claims["bodySha256"].(string)
74
if !ok {
75
return false, fmt.Errorf("bodySha256 claim is missing or not a string")
76
}
77
endpointUrl, ok := claims["endpointUrl"].(string)
78
if !ok {
79
return false, fmt.Errorf("endpointUrl claim is missing or not a string")
80
}
81
82
// Validate the reconstructed body hash and endpoint URL against the JWT payload
83
if reconstructedBodySha256 != bodySha256 || reconstructedEndpointUrl != endpointUrl {
84
return false, fmt.Errorf("verification failed: hashes or URLs do not match")
85
}
86
87
return true, nil
88
}
1
require 'json'
2
require 'openssl'
3
require 'jwt'
4
require 'net/http'
5
require 'uri'
6
7
def verify_webhook(request_payload, signature, request_url)
8
begin
9
# Convert the request body to a JSON string
10
json_str = JSON.generate(request_payload)
11
12
# Reconstruct the SHA-256 hash of the request body
13
sha256_digest = OpenSSL::Digest::SHA256.new
14
sha256_digest.update(json_str)
15
reconstructed_body_sha256 = Base64.strict_encode64(sha256_digest.digest)
16
17
# Reconstruct the full endpoint URL
18
reconstructed_endpoint_url = request_url
19
20
# Fetch the JWKS (JSON Web Key Set) for webhook verification
21
jwks_uri = URI.parse('https://api.evervault.com/jwks/webhooks.json')
22
jwks_response = Net::HTTP.get(jwks_uri)
23
jwks = JSON.parse(jwks_response)
24
jwk_set = JWT::JWK::Set.new(jwks['keys'])
25
26
# Verify the JWT using the JWKS
27
decoded_token = JWT.decode(signature, nil, false, jwks: jwk_set)
28
payload = decoded_token[0]
29
30
body_sha256 = payload['bodySha256']
31
endpoint_url = payload['endpointUrl']
32
33
# Validate the reconstructed body hash and endpoint URL against the JWT payload
34
if reconstructed_body_sha256 != body_sha256 || reconstructed_endpoint_url != endpoint_url
35
raise 'Body or endpoint URL could not be verified'
36
else
37
puts 'Webhook verified!'
38
true
39
end
40
rescue => e
41
puts "Webhook verification failed. #{e.message}"
42
false
43
end
44
end

3. Register your Webhook Endpoint with Evervault

After deploying your new endpoint, you can register it as a Webhook Endpoint with Evervault through either the Dashboard (Settings > Webhooks) or the Evervault API. You’ll need to subscribe this endpoint to the specific events you wish to receive. Once subscribed, Evervault will send those events to the registered endpoint whenever they occur for your app.

In this example, we are registering the newly created endpoint, https://hooks.acme.com/evervault, to listen for the payments.network-token.updated event.

Webhook Event Samples

Payments

View sample webhook events for the Payments API.

Get Started
Function Webhooks

View sample webhook events for asynchronous Function invocations and new deployments.

Get Started
Enclave Webhooks

View sample webhook events for new Enclave version deployments.

Get Started