How to process Karhoo webhooks
A better option than polling for Karhoo Marketplace events
Rather than requiring you to pull information via our API, webhooks will push information to your endpoint. When one of those events is triggered (for example a trip is updated by the fleet's driver), Karhoo will send this notification as an HTTP POST request, with a JSON body, to the endpoint you specify.
If you're new to webhooks, read this guide to learn more.
Registering your webhook url
You can enable directly using the webhooks register endpoint . Karhoo will need the url to send events to and a shared_secret to generate a HMAC authorization header.
{
"url": "https://your-company.com/karhoo/events",
"shared_secret": "dontT3llAnyoneThisSecret!"
}
Need to test a webhook?
A great way to test webhooks without building services is to use webhook.site which will allow you to sample the payloads we send from our sandbox environment. Do not use this for production trips.
Securing your webhook
Karhoo webhooks are secured using HMAC authentication using a shared secret.
The HMAC signature digest is computed on the payload content using the supplied secret key. This SHA512 hash is sent as lowercase hexits in the X-Karhoo-Request-Signature
header.
Always check each request
It is important to verify each request body to avoid man-in-the-middle attacks or fake requests.
The following is copied from a golang playground example of how we would sign our X-Karhoo-Request-Signature
header with the shared secret you set when subscribing to a webhook.
package main
import (
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"fmt"
)
//Please do not set your key to this example in production
const (
signature string = "8816883ca05dda771ddf522c26a958b262ebe52753ed5fcc87828b24aff49b3369aa005a2f664a87f1a1958e0f44121f1643aebcba35a32ff2d921eaad5e4ad7"
exampleSecretKey string = "EAlOTQ1IHwansbPn0cUOPyQYrONmuOAu"
sampleRequestBody string = `{"attempt_number":0,"checksum":"c7fe6dfefc4765337e8e0a31bbe1e49c2addd676c2f1b764100a0b27fb6f296c440ecb9c8f4aec552c86babe24ae1d7040cde399cc5153600799257a8a16f845","data":"{\"status\":\"ARRIVED\",\"trip_id\":\"c2374749-e983-4d8b-9312-1ca06a5ffe37\"}","event_type":"TripStatus","id":"5948ec35-a071-4f71-9416-c607d0120ca8","sent_at":"2020-12-02T00:04:58.711Z"}`
)
type Event struct {
ChecksumHEX string `json:"checksum"`
Data string `json:"data"`
}
// HashRequestBody represents a function similiar to how we sign our signature
func HashRequestBody(body []byte, secretKey []byte) (string, error) {
mac := hmac.New(sha512.New, secretKey)
_, err := mac.Write(body)
if err != nil {
return "", err
}
return hex.EncodeToString(mac.Sum(nil)), nil
}
func main() {
// we need to generate our own hash to compare against the signature
hash, err := HashRequestBody([]byte(sampleRequestBody), []byte(exampleSecretKey))
if err != nil {
panic(err)
}
// Will print true if the signature matches the hash we computed and our data is secure.
fmt.Println(signature == hash)
//We can go a step further and check the contents of the event type
var evt Event
if err := json.Unmarshal([]byte(sampleRequestBody), &evt); err != nil {
panic(err)
}
sha := sha512.Sum512([]byte(evt.Data))
checksum := hex.EncodeToString(sha[:])
fmt.Println(checksum == evt.ChecksumHEX)
}
You must hash the entire response body
Failing to account for linefeed (LF) or new line (NL) characters when comparing hash signatures will result in a failed cryptographic check.
Webhook Events
Successful delivery
A webhook is considered to be successfully delivered is a response is received with a 200 range status code (e.g. 200, 202, 204, etc.).
Retries
In the event of a failure defined as a request timeout, 400, 402-409 or 411-599 HTTP response codes, we will make 3 retry attempts.
- Immediately
- After 10 seconds
- After 30 seconds
Structure
All Karhoo webhook events have a common structure with the exception of the data
field which changes depending on the event_type
.
{
"id": "94282c25-f975-4aac-b86c-20929c8a066a",
"event_type": "TripStatus",
"sent_at": "2020-10-27T17:55:49.403Z",
"checksum": "c8968066ff2919fd80ad178da76794e6925b121c1d",
"attempt_number": 0,
"data": "{...}"
}
An Example: Trip Status Event
The Trip Status webhook works similarly to the polling endpoint for getting trip status.
{
"id": "94282c25-f975-4aac-b86c-20929c8a066a",
"event_type": "TripStatus",
"sent_at": "2020-10-27T17:55:49.403Z",
"checksum": "c8968066ff2919fd80ad178da76794e6925b121c1d",
"attempt_number": 0,
"data": "{\"trip_id\":\"b6a5f9dc-9066-4252-9013-be85dfa563bc\",\"status\":\"CONFIRMED\"}"
}
And here is the Trip Status
data object once decoded:
{
"trip_id": "b6a5f9dc-9066-4252-9013-be85dfa563bc",
"status": "CONFIRMED"
}
An Example: Driver Details Event
The Driver Details webhook event is sent if any driver or vehicle details change after the trip has been booked.
{
"id": "94282c25-f975-4aac-b86c-20929c8a066a",
"event_type": "DriverDetails",
"sent_at": "2020-10-27T17:55:49.403Z",
"checksum": "c8968066ff2919fd80ad178da76794e6925b121c1d",
"attempt_number": 0,
"data": "{\"trip_id\":\"b6a5f9dc-9066-4252-9013-be85dfa563bc\",\"description\":\"Renault Scenic (Black)\",\"driver\":{\"first_name\":\"Michael\",\"last_name\":\"Higgins\",\"phone_number\":\"+441111111111\",\"photo_url\":\"https:\/\/karhoo.com\/drivers\/mydriver.png\",\"license_number\":\"ZXZ151YTY\"},\"luggage_capacity\":2,\"passenger_capacity\":3,\"vehicle_class\":\"MPV\",\"vehicle_license_plate\":\"123 XYZ\"}"
}
And here is the Driver Details
object once you decode the request. You will need to decode into the relevant structures based on the event type.
{
"trip_id": "b6a5f9dc-9066-4252-9013-be85dfa563bc",
"description": "Renault Scenic (Black)",
"driver": {
"first_name": "Michael",
"last_name": "Higgins",
"phone_number": "+441111111111",
"photo_url": "https://karhoo.com/drivers/mydriver.png",
"license_number": "ZXZ151YTY"
},
"luggage_capacity": 2,
"passenger_capacity": 3,
"vehicle_class": "MPV",
"vehicle_license_plate": "123 XYZ"
}
An Example: Final Fare Event
Notification that the final fare for this trip has been set and is ready to be retrieved from the Fares API.
{
"attempt_number": 0,
"checksum": "3524be952b4135de7e7fbef2dc07d343f83b3e379e48afa4d72c87ed66758116238fb4c82e6d848bb5014020492cb1ee18d99b509742ce672452cd456444fab5",
"data": "{\"trip_id\":\"7edd8f27-4643-475e-b8af-84a98b091123\"}",
"event_type": "FinalFareReleased",
"id": "5ecfcded-4d8e-4444-a043-19d65d345k6l",
"sent_at": "2020-10-27T17:55:49.403Z"
}
Updated about 1 year ago