Webhooks
Learn how to set up your webhook endpoint, configure authentication, and receive Driver Events from Suntransfers.
Endpoint requirements
- An absolute
https://URL with a valid certificate. - The host name resolves to a public IP address. Local, private and reserved addresses (for example
localhost,10.0.0.0/8or192.168.0.0/16) are not reached. - The endpoint accepts
POSTrequests with a JSON body. - The endpoint answers directly. Redirects (
3xx) are not followed.
Set up your webhook
- Choose an authentication method. Review the supported options in Webhook Authentication.
- Provide your webhook details. Share your endpoint URL, preferred authentication method, and required configuration details with your Suntransfers contact.
- Test in Staging. Once your webhook has been configured, verify that your endpoint can receive and process Driver Events.
- Go live. After successful testing, coordinate with your Suntransfers contact to configure your webhook in Production.
Separate configurations are required for Staging and Production.
Webhook Authentication
Values each method needs:
| Method | Required | Optional |
|---|---|---|
| No auth | — | — |
| API key in query param | param, value | — |
| Custom header | header, value | — |
| Basic auth | username, password | — |
| OAuth2 client credentials | token_url, client_id, client_secret | scope, audience |
| HMAC payload signature | header, secret | algorithm |
Rules for these values:
- Every value is non-empty text.
headeris a standard HTTP header name: letters, digits and hyphens, with no spaces or:. It can't be a content header such asContent-TypeorContent-Length.- The
valueof a custom header is a single line of printable ASCII text. usernamecan't contain:.token_urlfollows the same rules as your endpoint: an absolutehttps://URL whose host resolves to a public IP address.algorithmissha256(the default) orsha512, in lowercase.
Authentication Methods
No Authentication
The request carries no credentials. Use it only if your endpoint is protected in another way.
API Key in Query Param
You provide a parameter name (param) and a value (value):
{
"param": "key",
"value": "<api key>"
}Both are URL-encoded and added to the query of your URL. If your URL already has a query, the parameter is added after the existing ones with &.
POST /driver-events?key=abc123 HTTP/1.1
Host: webhooks.partner.example.comPOST /driver-events?source=suntransfers&key=abc123 HTTP/1.1
Host: webhooks.partner.example.comCustom Header
You provide a header name (header) and its value (value). The header is sent exactly as you give it, and replaces any header with the same name.
{
"header": "Authorization",
"value": "Bearer <token>"
}POST /driver-events HTTP/1.1
Host: webhooks.partner.example.com
Authorization: Bearer <token>To send an API key in a header, use a name such as X-API-Key with the bare key as the value:
POST /driver-events HTTP/1.1
Host: webhooks.partner.example.com
X-API-Key: abc123Basic Authentication
You provide a username and a password. Each request carries an Authorization header with the Base64 encoding of username:password.
{
"username": "partner-user",
"password": "partner-pass"
}POST /driver-events HTTP/1.1
Host: webhooks.partner.example.com
Authorization: Basic cGFydG5lci11c2VyOnBhcnRuZXItcGFzcw==OAuth2 Client Credentials
You provide the URL of your token endpoint (token_url), a client_id and a client_secret, and optionally a scope and an audience.
{
"token_url": "https://auth.example.com/oauth/token",
"client_id": "my-client-id",
"client_secret": "my-client-secret",
"scope": "driver-events",
"audience": "driver-events-api"
}Token request. Suntransfers gets an access token from your token endpoint with the client credentials grant:
- a
POSTtotoken_urlwith a form-encoded body:grant_type=client_credentials, plusscopeandaudiencewhen you provide them; - the client authenticates with HTTP Basic:
client_idandclient_secretare URL-encoded, joined with:and Base64-encoded.
POST /oauth/token HTTP/1.1
Host: auth.example.com
Authorization: Basic bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=driver-events&audience=driver-events-apiToken response. It must be a JSON object with access_token. expires_in is the token's lifetime in seconds, as a number or a numeric string.
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}Using the token. Each request carries the token in an Authorization header:
POST /driver-events HTTP/1.1
Host: webhooks.partner.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...- The token is reused until shortly before it expires. Without
expires_in, a token is used for one request only. - If your endpoint answers
401, a new token is requested and the event is sent once more. If that request also gets a401, the event is not delivered. - If your token endpoint rejects the credentials or its response has no
access_token, the event is not delivered.
HMAC Payload Signature
You provide a header name (header), a shared secret (secret) and, optionally, the algorithm (algorithm: sha256, the default, or sha512).
{
"header": "X-Signature",
"secret": "<secret>",
"algorithm": "sha256"
}Each request carries the signature of its body in that header:
POST /driver-events HTTP/1.1
Host: webhooks.partner.example.com
Content-Type: application/json; charset=utf-8
X-Signature: 887c469c3d0d44ad40f495c4586d07db143406216c61fcaacabdf0dadf46d23cVerifying the HMAC signature
How the signature is computed:
- Signed content: the exact bytes of the request body, as received.
- Key: the UTF-8 bytes of the shared secret.
- Algorithm: HMAC-SHA256, or HMAC-SHA512 if agreed.
- Header value: the digest in lowercase hexadecimal, with no prefix: 64 characters for SHA-256, 128 for SHA-512.
To verify a request:
- Read the raw request body before parsing the JSON. Parsing and serializing it again can change the bytes and break the signature.
- Compute the HMAC of the raw body with your secret.
- Compare it with the header value using a constant-time comparison.
- If they don't match, reject the request with
401.
const crypto = require('crypto');
// rawBody: the request body exactly as received (Buffer), before parsing the JSON
function isValidSignature(rawBody, signature, secret, algorithm = 'sha256') {
const expected = crypto.createHmac(algorithm, Buffer.from(secret, 'utf8')).update(rawBody).digest('hex');
const received = Buffer.from(signature || '', 'utf8');
return received.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), received);
}import hashlib
import hmac
ALGORITHMS = {"sha256": hashlib.sha256, "sha512": hashlib.sha512}
# raw_body: the request body exactly as received (bytes), before parsing the JSON
def is_valid_signature(raw_body: bytes, signature: str, secret: str, algorithm: str = "sha256") -> bool:
expected = hmac.new(secret.encode("utf-8"), raw_body, ALGORITHMS[algorithm]).hexdigest()
return hmac.compare_digest(expected, signature or "")using System.Security.Cryptography;
using System.Text;
// rawBody: the request body exactly as received, before parsing the JSON
static bool IsValidSignature(byte[] rawBody, string? signature, string secret, string algorithm = "sha256")
{
var key = Encoding.UTF8.GetBytes(secret);
var hash = algorithm switch
{
"sha256" => HMACSHA256.HashData(key, rawBody),
"sha512" => HMACSHA512.HashData(key, rawBody),
_ => throw new ArgumentException($"Unsupported algorithm: {algorithm}", nameof(algorithm))
};
var expected = Encoding.ASCII.GetBytes(Convert.ToHexStringLower(hash));
var received = Encoding.ASCII.GetBytes(signature ?? "");
return CryptographicOperations.FixedTimeEquals(expected, received);
}Test vector
Use these values to check your implementation:
| Value | |
|---|---|
| Secret | my-shared-secret |
| Body | [{"bookingReference":"SUNTR_AB1234","externalReference":"AGENCY_REF_1","transferPosition":2,"eventType":"DRIVER_ARRIVED_AT_PICKUP","occurredAt":"2026-07-08T18:10:00+02:00","latitude":41.38,"longitude":2.17}] |
Signature (sha256) | 887c469c3d0d44ad40f495c4586d07db143406216c61fcaacabdf0dadf46d23c |
Signature (sha512) | a7c1b26c6530df3cc5ee4aabeb7976875ff6f15523d9ce6ea6bcc90b4f2b712ce874cc79c551dc04aaa674e4a8b0745d2bb4ed9842e076b3a781eb1d61642c2e |
Request format
Each event is sent as a POST request to your endpoint:
Content-Type: application/json; charset=utf-8- The body is a JSON array of events, as described in Receive Driver Events, sent as compact JSON with no spaces or line breaks.
POST /driver-events HTTP/1.1
Host: webhooks.partner.example.com
Content-Type: application/json; charset=utf-8
Content-Length: 207
[{"bookingReference":"SUNTR_AB1234","externalReference":"AGENCY_REF_1","transferPosition":2,"eventType":"DRIVER_ARRIVED_AT_PICKUP","occurredAt":"2026-07-08T18:10:00+02:00","latitude":41.38,"longitude":2.17}]The request also carries the header or query parameter of your authentication method, as shown below.
Responding to events
- Answer with any
2xxstatus to confirm that you received the event. The response body is ignored. - Answer quickly and process the event afterwards, for example by storing it and handling it in the background.
Updated about 2 hours ago