Integrating webhooks
Discover how you can listen to Transatel events by registering webhooks
Learn how to receive Transatel events on your platform using the steps in this guide:
- Identify relevant events for your use cases and their payload definition
- Create a webhook endpoint accepting
HTTP POST JSON
payload - Handle Transatel events and execute your own logic
- Check event signature
- Register your webhook for the selected events
- Test your integration by sending test events of your choice
- Activate your webhook whenever you are ready
- Implement mTLS authentication
Endpoints
Below are all APIs url that you will need for this guide:
Transatel APIs | URL |
---|---|
Authentication | https://api.transatel.com/authentication/api |
Webhooks | https://api.transatel.com/webhooks/api |
All Transatel APIs are available through our unique API gateway https://api.transatel.com
1. Identify relevant events
Start by reviewing all available Transatel events and identify which ones may be useful to trigger your logic:
Then review the specifications of those events that your endpoint will have to process.
2. Create a webhook endpoint
To receive events, you will need to create an HTTP
endpoint accepting POST
requests of JSON
payload.
This endpoint should in return respond with a HTTP/1.1 204 No content
.
You can of course, implement this endpoint in the language / framework of your choice. Below a simple example in Java
using the Spark micro framework.
import static spark.Spark.port;import static spark.Spark.post;
public class WebhookEndpoint { public static void main(String[] args) {
port(8080);
post("/events", (request, response) -> { response.status(204); return ""; }); }}
3. Handle Transatel events
Now that your endpoint is defined, you will need to add the event processing logic.
It's generally a 3 steps implementation:
- Parse the event and detect its type
- Execute / trigger your specific logic
- Returns a
HTTP/1.1 204 No content
response
Parse the event
Every Transatel event shares a common structure with an header
holding information such as the eventId
or the eventType
and a body
holding all other information specific to the event.
Below is a partial example of an event:
{ "header": { "eventId": "627e77fc-7694-4188-8f19-13ca3dbf8f51", "eventType": "OCS/PRODUCT/ACTIVATED", "eventDate": "2023-04-19T13:30:00Z" }, "body": { "mvnoRef": "<Customer account name>", "cos": "<Name of the class of service>", "msisdn": "33612345678", "iccid": "8988247076000000319", "externalReference": "SCOT78LH27", ... }}
Parsing the header
will allow you to detect which type of event you receive and as such determine which logic you have to trigger.
The body
will also inform your of the detail of the event and more specifically which subscriber was concerned thanks to the msisdn
and iccid
fields.
Very important, when parsing events, always:
- declare only useful fields
- systematically ignore all unknown fields
This will guarantee your integration to be compatible with future backward compatible evolutions.
Execute your specific logic
Once you have detected which type of event you received and for which subscriber, you can execute your specific logic based on your use cases.
For complex logic, we recommend that you perform it asynchronously to avoid timeout issues.
Return a 204 response
Finally, your endpoint should respond with a HTTP/1.1 204 No content
indicating that the event was well-received.
In case, your endpoint incorrectly responds with a KO response status (3xx
, 4xx
, 5xx
) , the event delivery will be automatically retried.
Below is an example of implementation:
import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;import lombok.extern.slf4j.Slf4j;
import static spark.Spark.port;import static spark.Spark.post;
@Slf4jpublic class WebhookEndpoint { public static void main(String[] args) {
final ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule());
port(8080);
post("/events", (request, response) -> {
final var event = objectMapper.readValue(request.body(), Event.class); final var eventType = event.getHeader().getEventType();
log.info("Received an {} event for subscriber: {}", eventType, event.getBody().getMsisdn());
switch (eventType) { case OCS_PRODUCT_ACTIVATED: // Execute your specific product activation logic break; case OCS_PRODUCT_EXPIRED: // Execute your specific product expiration logic break; //... }
response.status(204); return ""; }); }}
Now that your endpoint is ready and before registering it in your webhooks, you will need to make it publicly accessible over HTTPS
.
4. Check event signature (recommended)
As an extra step, we highly recommended that you secure your webhook integration by verifying the signature sent with the events. This will guarantee that those events are originating from our platform and not from another server acting as Transatel.
To do so, two things are required:
- Define a secret when registering your webhook
- Verify the signature of each event received on your endpoint
Define a secret on your webhook
This is as easy as adding a field secret
when creating your webhook (See below for explanations on how to create your webhook). This secret will then be used by our platform to sign all events sent to your webhook endpoint.
The signature will be provided as a X-TSL-Signature-256
HTTP
header with the following definition: sha256=hex(HMACSHA256(payload))
(Example: sha256=a4ecb55b23be547f1b82e24f46beef8a0856675d85bd5baebd9ef0e3a05e7e3e
)
Verify the payload signature
To verify, the signature you will need to adapt a little your endpoint implementation in order to compute the signature based on the secret
you know and compare it to the signature received with the event.
Only if those signatures match you should accept the event and proceed further.
Below is the previous implementation example modified to include payload signature verification:
import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;import lombok.extern.slf4j.Slf4j;import org.apache.commons.codec.digest.HmacAlgorithms;import org.apache.commons.codec.digest.HmacUtils;import spark.Request;
import static spark.Spark.*;import static spark.utils.StringUtils.isEmpty;
@Slf4jpublic class WebhookEndpoint {
private static final String HEADER_HMAC_SIGNATURE = "X-TSL-Signature-256"; private static final String HMAC_PREFIX = "sha256="; private static final String SECRET = "my-secret";
public static void main(String[] args) {
final ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule());
port(8080);
post("/events", (request, response) -> {
checkSignature(request);
final var event = objectMapper.readValue(request.body(), Event.class); final var eventType = event.getHeader().getEventType();
log.info("Received an {} event for subscriber: {}", eventType, event.getBody().getMsisdn());
switch (eventType) { case OCS_PRODUCT_ACTIVATED: // Execute your specific product activation logic break; case OCS_PRODUCT_EXPIRED: // Execute your specific product expiration logic break; //... }
response.status(204); return ""; }); }
private static void checkSignature(Request req) { final String requestSignature = req.headers(HEADER_HMAC_SIGNATURE);
if (isEmpty(requestSignature)) { log.error("Missing payload signature"); halt(403); }
final String sign = HMAC_PREFIX + new HmacUtils(HmacAlgorithms.HMAC_SHA_256, SECRET) .hmacHex(req.bodyAsBytes()); if (!sign.equals(requestSignature)) { log.error("Invalid request signature"); halt(403); } }}
5. Register your webhook
First thing first, let's get your access token
by following the getting started guide!
Now that everything is ready to receive those events, let's register your first webhook. In this example below, we will only subscribe to OCS product activation event, but you can of course subscribe to any events of your choice depending on your requirements.
To do so, we will use the Webhooks API.
The below example shows how to register an inactive webhook and subscribe to the OCS product activation event:
curl --location --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJidXNpbmVzc19lbnRpdGllcyI6WyJBbnkiXSwicHJpbmNpcGFsIjoib2NzLXN1YnNjcmlwdGlvbi1vcmRlci1tYW5hZ2VyIiwic2NvcGUiOlsiQ0FUQUxPR19QUk9EVUNUU19SRUFEX1RFQ0hOSUNBTCIsIkNBVEFMT0dfUFJPRFVDVFNfUkVBRCIsIlVTRVJfUkVBRCIsIklOVkVOVE9SWV9TVUJTQ1JJUFRJT05TX1JFQUQiXSwiZXhwIjoxNjE3MjYwNTYxLCJhdXRob3JpdGllcyI6WyJST0xFX0lOVEVSTkFMX1NZU1RFTSJdLCJqdGkiOiJmOTYwNTFmNS05ZmQzLTQxMDEtYjNkZC0xZjY2Y2Q2MWUzZjIiLCJjbGllbnRfaWQiOiJvY3Mtc3Vic2NyaXB0aW9uLW9yZGVyLW1hbmFnZXIifQ.KBTlltd459_4kPV0O3OfsBTFGBtoqRxG65o_NohK1U7IuJAvIjAa8Fj4Qon1ptFrrTR5M2o8l0f9Dl0D7r0Us6Ej6OsVlFXfQKUiXwVytNUTP7POB6l56Svc5CaVqkXgFKyt7_8h6Ii6R_RteujtSzJSWX7zeLSeemxsxKNtHLLIS_HLyNdQyIsVCZWRfFhdPJVQId_p6B08_A54sAwhrT7qssSbtpG1nGzdqsHdFfaqJt9ABhPedatHNlwcWtCnFthrOrD2rv0Yf8OnRLnt-ZWtypr0cqm6U-brtZeBMhcxgzF4afKlJbKN3K3drPJRrRPjL8L9bf2YvzsDWxY5lA' \ --data \ "{ \"mvnoRef\": \"M2MA_WW_TSL_MWC_INTERNAL\", \"status\": \"inactive\", \"targetUrl\": \"https://example.com/events\", \"secret\": \"my-secret\", \"events\": [ \"OCS/PRODUCT/ACTIVATED\" ] }" \ 'https://api.transatel.com/webhooks/api/webhooks'
Above are highlighted the main information to provide to the API:
Authorization
header: This is where you provide the previously retrievedaccess_token
mvnoRef
: Customer account identifier provided by Transatel at setupstatus
: The status of the webhook, hereinactive
which means that events occurring on the platform won't be sent immediately to your endpointtargetUrl
: The HTTPS url to which events will be postedsecret
: An optional but recommended secret used to sign the payloadevents
: An array with the events you want to subscribe to, here the product activation event
If successful, the response returned will be as follows:
HTTP/1.1 201 CreatedContent-Type: application/json{ "id": "257728ad-01ed-447d-b923-062604926f95", "mvnoRef": "M2MA_WW_TSL_MWC_INTERNAL", "status": "inactive", "targetUrl": "https://example.com/events", "events": [ { "eventType": "OCS/PRODUCT/ACTIVATED", "description": "A subscription to a product has been activated", "domain": "OCS", "category": "products", "_links": { "doc": { "href": "https://developers.transatel.com/api/ocs/events/#tag/Products/paths/OCS~1PRODUCT~1ACTIVATED/post" } } } ], "_links": { "self": { "href": "https://api.transatel.com/webhooks/api/webhooks/257728ad-01ed-447d-b923-062604926f95" } }}
A successful response returns a HTTP/1.1 201 Created
with the details of the webhook.
We recommend that at first you create webhooks as inactive, this way you won't receive any platform event yet, giving you time to validate your integration thanks to our simulation event operation.
6. Test your integration
You are now ready to test your webhook by simulating an OCS product activation event!
To do so, we will use the Webhooks API.
The below example shows how to simulate an OCS product activation event:
curl --location --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJidXNpbmVzc19lbnRpdGllcyI6WyJBbnkiXSwicHJpbmNpcGFsIjoib2NzLXN1YnNjcmlwdGlvbi1vcmRlci1tYW5hZ2VyIiwic2NvcGUiOlsiQ0FUQUxPR19QUk9EVUNUU19SRUFEX1RFQ0hOSUNBTCIsIkNBVEFMT0dfUFJPRFVDVFNfUkVBRCIsIlVTRVJfUkVBRCIsIklOVkVOVE9SWV9TVUJTQ1JJUFRJT05TX1JFQUQiXSwiZXhwIjoxNjE3MjYwNTYxLCJhdXRob3JpdGllcyI6WyJST0xFX0lOVEVSTkFMX1NZU1RFTSJdLCJqdGkiOiJmOTYwNTFmNS05ZmQzLTQxMDEtYjNkZC0xZjY2Y2Q2MWUzZjIiLCJjbGllbnRfaWQiOiJvY3Mtc3Vic2NyaXB0aW9uLW9yZGVyLW1hbmFnZXIifQ.KBTlltd459_4kPV0O3OfsBTFGBtoqRxG65o_NohK1U7IuJAvIjAa8Fj4Qon1ptFrrTR5M2o8l0f9Dl0D7r0Us6Ej6OsVlFXfQKUiXwVytNUTP7POB6l56Svc5CaVqkXgFKyt7_8h6Ii6R_RteujtSzJSWX7zeLSeemxsxKNtHLLIS_HLyNdQyIsVCZWRfFhdPJVQId_p6B08_A54sAwhrT7qssSbtpG1nGzdqsHdFfaqJt9ABhPedatHNlwcWtCnFthrOrD2rv0Yf8OnRLnt-ZWtypr0cqm6U-brtZeBMhcxgzF4afKlJbKN3K3drPJRrRPjL8L9bf2YvzsDWxY5lA' \ --data \ "{ \"eventType\": \"OCS/PRODUCT/ACTIVATED\" }" \ 'https://api.transatel.com/webhooks/api/webhooks/257728ad-01ed-447d-b923-062604926f95'
Above are highlighted the main information to provide to the API:
Authorization
header: This is where you provide the previously retrievedaccess_token
eventType
: The event type of the event you want to simulatewebhookId
: The ID of the webhook in the URL for which we need to send the simulation event
If successful, the response returned will be as follows:
HTTP/1.1 200 OK{ "success": true, "response": { "status": "204 NO_CONTENT", "headers": { "foo": "bar" }, "body": { "field": "value" } }}
A successful response returns a HTTP/1.1 200 OK
with the details of the response that we received from your webhook endpoint.
This operation can be used to:
- Troubleshoot communication issues between our platform and yours
- Validate the correct implementation on your webhook endpoint by simulating the different Transatel events
7. Activate your webhook
Now that everything works great, let's activate your webhook and start receiving real events!
To do so, we will use the Webhooks API.
The below example shows how to update the webhook and switch its status
to active
:
curl --location --request PUT \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJidXNpbmVzc19lbnRpdGllcyI6WyJBbnkiXSwicHJpbmNpcGFsIjoib2NzLXN1YnNjcmlwdGlvbi1vcmRlci1tYW5hZ2VyIiwic2NvcGUiOlsiQ0FUQUxPR19QUk9EVUNUU19SRUFEX1RFQ0hOSUNBTCIsIkNBVEFMT0dfUFJPRFVDVFNfUkVBRCIsIlVTRVJfUkVBRCIsIklOVkVOVE9SWV9TVUJTQ1JJUFRJT05TX1JFQUQiXSwiZXhwIjoxNjE3MjYwNTYxLCJhdXRob3JpdGllcyI6WyJST0xFX0lOVEVSTkFMX1NZU1RFTSJdLCJqdGkiOiJmOTYwNTFmNS05ZmQzLTQxMDEtYjNkZC0xZjY2Y2Q2MWUzZjIiLCJjbGllbnRfaWQiOiJvY3Mtc3Vic2NyaXB0aW9uLW9yZGVyLW1hbmFnZXIifQ.KBTlltd459_4kPV0O3OfsBTFGBtoqRxG65o_NohK1U7IuJAvIjAa8Fj4Qon1ptFrrTR5M2o8l0f9Dl0D7r0Us6Ej6OsVlFXfQKUiXwVytNUTP7POB6l56Svc5CaVqkXgFKyt7_8h6Ii6R_RteujtSzJSWX7zeLSeemxsxKNtHLLIS_HLyNdQyIsVCZWRfFhdPJVQId_p6B08_A54sAwhrT7qssSbtpG1nGzdqsHdFfaqJt9ABhPedatHNlwcWtCnFthrOrD2rv0Yf8OnRLnt-ZWtypr0cqm6U-brtZeBMhcxgzF4afKlJbKN3K3drPJRrRPjL8L9bf2YvzsDWxY5lA' \ --data \ "{ \"mvnoRef\": \"M2MA_WW_TSL_MWC_INTERNAL\", \"status\": \"active\", \"targetUrl\": \"https://example.com/events\", \"secret\": \"my-secret\", \"events\": [ \"OCS/PRODUCT/ACTIVATED\" ] }" \ 'https://api.transatel.com/webhooks/api/webhooks/257728ad-01ed-447d-b923-062604926f95'
Above are highlighted the main information to provide to the API:
Authorization
header: This is where you provide the previously retrievedaccess_token
status
: The status of the webhook, here we update it toactive
webhookId
: The ID of the webhook in the URL for which we need to send the simulation event
If successful, the response returned will be as follows:
HTTP/1.1 200 OKContent-Type: application/json{ "id": "257728ad-01ed-447d-b923-062604926f95", "mvnoRef": "M2MA_WW_TSL_MWC_INTERNAL", "status": "active", "targetUrl": "https://example.com/events", "events": [ { "eventType": "OCS/PRODUCT/ACTIVATED", "description": "A subscription to a product has been activated", "domain": "OCS", "category": "products", "_links": { "doc": { "href": "https://developers.transatel.com/api/ocs/events/#tag/Products/paths/OCS~1PRODUCT~1ACTIVATED/post" } } } ], "_links": { "self": { "href": "https://api.transatel.com/webhooks/api/webhooks/257728ad-01ed-447d-b923-062604926f95" } }}
A successful response returns a HTTP/1.1 200 OK
with the details of the webhook.
8. Implement mTLS authentication (recommended)
As an extra step, we highly recommend that you secure your webhook integration by implementing mTLS.
mTLS ensures that the parties at each end of a network connection are who they claim to be by verifying that they both have the correct private key
.
To do so, two things are required:
- Implement Transatel certificate CA chain in your truststore
- Verify the CN client certificate received on your endpoint
Implement Transatel certificate CA chain in your truststore
Regardless of your HTTPS
endpoint, you must implement a trusted list of certificate authorities
. It’s often a PEM
file containing a concatenated list of trusted CAs
, base64 encoded.
Transatel use Sectigo CA
as public trusted authority, with this complete chain:
- webservice-client.transatel.com - subject: CN = webservice-client.transatel.com
- Intermediate 1 - subject: C = GB, ST = Greater Manchester, L = Salford, O = Sectigo Limited, CN = Sectigo RSA Domain Validation Secure Server CA
- Intermediate 2 - subject: C = US, ST = New Jersey, L = Jersey City, O = The USERTRUST Network, CN = USERTrust RSA Certification Authority
- Root CA - subject: C = GB, ST = Greater Manchester, L = Salford, O = Comodo CA Limited, CN = AAA Certificate Services
Your truststore can be a concatenation from intermediate 1 to Root CA from certificates page.
At this point, the hand-check can be performed with all certificates signed by this chain.
Sample of nginx configuration:
server { listen 443 ssl default_server; ssl_verify_client on; ssl_client_certificate /path/to/trustore.pem; ssl_verify_depth 2; ...}
Sample of Apache2 configuration:
<VirtualHost *:443> SSLVerifyClient require SSLCACertificateFile "/path/to/trustore.pem" SSLVerifyDepth 2 ...</VirtualHost>
Verify the CN client certificate received on your endpoint
Now, at HTTP
level, you can filter allowed connections to CN webservice-client.transatel.com client
certificates.
With ngnix:
location / { if ($ssl_client_s_dn !~ ‘webservice-client.transatel.com’) { return 403;}
With Apache2:
<Directory /var/www/> # or Location/Proxy ... Require expr "%{SSL_CLIENT_S_DN_CN} == 'webservice-client.transatel.com'" ...</Directory>
What to do next?
Learn more about our Webhooks API and Transatel events by checking API references and overviews: