Webhooks
Search…
Create the webhook endpoint

Defining webhook endpoints

Webhook endpoints are standard application endpoints designed to accept HTTP POST requests. Technically, creating a webhook endpoint is just as similar as creating any other page on your application. Key points to note while creating a webhook include:
  • If you are still developing your endpoint on a local machine, it can be a HTTP URL.
  • Once your endpoint is deployed to production, it must be a HTTPS URL.
  • Your endpoint can parse the callback event and delegate suitably to specific handlers based on the event type.
Once you have determined the event you want to listen for, set up an HTTP endpoint on your local machine that can accept unauthenticated webhook requests via a POST method.
Ruby
Java
Go
1
require 'sinatra'
2
require 'openssl'
3
require 'json'
4
5
# Find your webhooks signing key in your webhook settings in
6
# your Reloadly Dashboard
7
signing_secret = 'put_your_webhook_signing_secret_here...'
8
9
# Using the Sinatra framework
10
set :port, 4242
11
12
post '/your/webhook/url' do
13
payload = request.body.read
14
request_signature = request.env['X-Reloadly-Signature']
15
request_timestamp = request.env['X-Reloadly-Request-Timestamp']
16
17
computed_signature = nil
18
data_to_sign = payload + ":" + request_timestamp
19
begin
20
digest = OpenSSL::Digest.new('sha256')
21
computed_signature = OpenSSL::HMAC.hexdigest(digest, signing_key, data_to_sign)
22
rescue JSON::ParserError => e
23
# Invalid payload
24
status 400
25
return
26
end
27
28
# Verify that the computed signature matches the request_signature
29
if computed_signature != request_signature
30
return 400
31
end
32
33
# Handle the event
34
event = JSON.parse(payload, object_class: MyClass)
35
case event['type']
36
when 'airtime_transaction.status'
37
puts 'Processing airtime transaction!'
38
when 'giftcard_transaction.status'
39
puts 'Processing giftcard transaction!'
40
# ... handle other event types
41
else
42
puts "Unhandled event type: #{event['type']}"
43
end
44
45
status 200
46
end
Copied!
1
import javax.crypto.Mac;
2
import javax.crypto.spec.SecretKeySpec;
3
import java.nio.charset.StandardCharsets;
4
import java.security.InvalidKeyException;
5
import java.security.NoSuchAlgorithmException;
6
import java.time.Duration;
7
import java.time.Instant;
8
import java.time.LocalDateTime;
9
import java.util.TimeZone;
10
import org.json.JSONObject;
11
12
// Find your webhooks signing key in your webhook settings in your
13
// Reloadly Dashboard
14
String signingKey = "put_your_webhooks_signing_key_here...";
15
16
// Using the Spark framework (http://sparkjava.com)
17
public Object handle(Request request, Response response) {
18
19
String payload = request.body();
20
String requestSignature = request.headers("X-Reloadly-Signature");
21
String requestTimestamp = request.headers("X-Reloadly-Request-Timestamp");
22
23
StringBuilder computedSignature = new StringBuilder();
24
try {
25
26
LocalDateTime requestDatetime = LocalDateTime.ofInstant(
27
Instant.ofEpochSecond(timestamp), TimeZone.getDefault().toZoneId());
28
29
if (Duration.between(requestDatetime, LocalDateTime.now())
30
.getSeconds() > 300) {
31
32
/*
33
* If the request timestamp is more than five minutes from local time, it could be a replay attack,
34
* so let's ignore it.
35
*/
36
throw new IllegalStateException("Possible replay attack!");
37
}
38
39
final String ALGORITHM = "HmacSHA256";
40
Mac hasher = Mac.getInstance(ALGORITHM);
41
String dataToSign = payload + ":" + requestTimestamp;
42
hasher.init(
43
new SecretKeySpec(signingKey.getBytes(StandardCharsets.UTF_8), ALGORITHM)
44
);
45
byte[] hash = hasher.doFinal(dataToSign.getBytes(StandardCharsets.UTF_8));
46
47
for (byte b : hash) {
48
computedSignature.append(Integer.toString((b & 0xff) + 0x100, 16)
49
.substring(1));
50
}
51
52
} catch (Exception e) {
53
// Invalid payload
54
response.status(400);
55
return "";
56
}
57
58
// Verify signature
59
if (!computedSignature.toString().equal(requestSignature)) {
60
// Invalid signature
61
response.status(400);
62
return "";
63
}
64
65
// Parse the request payload to JSONObject
66
JSONObject event = new JSONObject(paload);
67
68
// Handle the event
69
switch (event.getString("type")) {
70
case "airtime_transaction.status":
71
System.out.println("Processing airtime transaction!");
72
break;
73
case "giftcard_transaction.status":
74
System.out.println("Processing giftcard transaction!");
75
break;
76
// ... handle other event types
77
default:
78
System.out.println("Unhandled event type: " + event.getString("type"));
79
}
80
81
response.status(200);
82
return "";
83
}
Copied!
1
import (
2
"crypto/hmac"
3
"crypto/sha256"
4
"encoding/hex"
5
"fmt"
6
"strconv"
7
)
8
9
http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) {
10
11
const MaxBodyBytes = int64(65536)
12
req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes)
13
payload, err := ioutil.ReadAll(req.Body)
14
if err != nil {
15
fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err)
16
w.WriteHeader(http.StatusServiceUnavailable)
17
return
18
}
19
20
requestSignature := req.Header.Get("X-Reloadly-Signature")
21
requestTimestamp := req.Header.Get("X-Reloadly-Request-Timestamp")
22
23
// Find your webhooks signing key in your webhook settings in your
24
// Reloadly Dashboard
25
signingKey := "put_your_webhooks_signing_key_here...";
26
27
dataToSign := request + ":" + strconv.Itoa(requestTimestamp)
28
29
// Create a new HMAC by defining the hash type and the key (as byte array)
30
hash, err := hmac.New(sha256.New, []byte(signingKey))
31
if err != nil {
32
fmt.Fprintf(os.Stderr, "Error generating signature: %v\n", err)
33
// Return a 400 error on a bad signature
34
w.WriteHeader(http.StatusBadRequest)
35
return
36
}
37
38
// Write Data to it
39
hash.Write([]byte(dataToSign))
40
41
// Get result and encode as hexadecimal string
42
computedSignature := hex.EncodeToString(hash.Sum(nil))
43
44
if computedSignature != requestSignature {
45
fmt.Fprintf(os.Stderr, "Error invalid signature: %v\n", err)
46
// Return a 400 error on a bad signature
47
w.WriteHeader(http.StatusBadRequest)
48
return
49
}
50
51
// Unmarshal the event request into an appropriate struct depending on its Type
52
switch event.Type {
53
case "airtime_transaction.status":
54
var airtimeTransaction := AirtimeTransactionStruct{}
55
err := json.Unmarshal(event.Data, &airtimeTransaction)
56
if err != nil {
57
fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err)
58
w.WriteHeader(http.StatusBadRequest)
59
return
60
}
61
fmt.Println("Processing airtime transaction!")
62
case "giftcard_transaction.status":
63
var giftcardTransaction := GiftCardTransactionStruct{}
64
err := json.Unmarshal(event.Data, &giftcardTransaction)
65
if err != nil {
66
fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err)
67
w.WriteHeader(http.StatusBadRequest)
68
return
69
}
70
fmt.Println("Processing giftcard transaction!")
71
// ... handle other event types
72
default:
73
fmt.Fprintf(os.Stderr, "Unhandled event type: %s\n", event.Type)
74
}
75
76
w.WriteHeader(http.StatusOK)
77
})
Copied!
In the example above, the /reloadly_webhook route is configured to accept POST requests and will only accept a JSON payload as request data via the reloadly_payload variable.
Copy link