Webhooks

More about Webhooks

Subscribing to notifications

You can choose which topics (or event flows as we call them) you're interested in when requesting an APP. Of course, you can also edit your existing apps to subscribe or unsubscribe to event flows.

To subscribe to a flow you need to provide the information required when requesting an APP.

๐Ÿ“˜

Callback URL

We expect that your callback URL responds within 2 seconds of receiving our notifications. If this happens and the response is not an HTTP 2xx code, we will retry 32 times with 30 minutes between each attempt (about 16 hours). If your callback URL is slower than 2 seconds or doesn't acknowledge with a 2xx code after all retry attempts, the notification will be dropped and cannot be recovered.

๐Ÿšง

Validate the Signature

Since your callback URL will be publicly accessible, it is highly recommended that you validate the signature of received messages, to make sure that it comes from the OLX API platform and has not been tampered with. More about this below.

๐Ÿšง

At-Least-Once Delivery and order of events

Due to the implementation details of our architecture, it's possible that you receive more than one notification for the same event. You should design your application to be idempotent (which means it should handle duplicate notifications gracefully).

Additionally, because of the distributed nature of our system, the order in which you receive events or notifications is not guaranteed. You should always rely on the timestamp provided in every notification to order them.

Notification format

All notification messages conform to a standard format, as shown here:

{
  "transaction_id": "e9a0b0c8-6aad-4a63-8679-501bd2d28761",
  "object_id": "9de9b5e3-1720-11e8-8790-0242ac110002",
  "destination": "https://myawesomeapp.com/webhooks",
  "timestamp": 1517236809256,
  "event_timestamp": 1517236809256,
  "flow": "publish_advert",
  "event_type": "advert_posted_success",
  "error_message": "",
  "data": { ... }
}

Note that the data field will contain data that are specific to any given notification, so its content will vary accordingly. This is important because your application must be prepared to deal with those situations. Here's a brief explanation of each field in this payload:

FieldDescription
transaction_idThe unique identifier of the message. It is used to identify and track the flow of requests within the API
object_idThe unique identifier of the data - for instance, if the flow is an export of an advert, the object_id would be the identifier of the advert being exported
destinationThe registered callback url of the subscriber (the URL to which this message will be sent)
timestampThe timestamp of what the event describes (the state change, the message creation, etc.) in milliseconds
event_timestampThe date of the creation of the event (in milliseconds)
flowThe name of the flow to which this notification refers to
event_typeThe event that triggered the notification
dataAdditional data related to a given notification

Notice that all requests made by our API to the configured endpoint will use the header "user-agent" with the value "olx-group-api".

Additionally, a custom header named x-signature will be present for each notification, containing an Hexademical HMAC (using SHA1) signature of the object_id and transaction_id in the following format: <OBJECT_ID>,<TRANSACTION_ID>

According to this, for the previous notification example and assuming you have provided the word "mywonderfulsecret" as your application's secret, the x-signature header would be:
a7b00386657384a3738d45462749d5a1b0ebd1e7

Check the section Signature validation to learn how to calculate the HMAC for notification such as the one above in different programming languages (Java, PHP, Go, C# and Python).

Signature validation

All the following examples assume that:

  • "signatureHeader" contains the "x-signature" header value
  • "notification" is a variable of a class that represents the notification structure
  • "secret" contains the secret configured for your application
package com.olx.example;

import com.olx.ecosystem.util.HmacSha1SignatureUtil;
import java.security.GeneralSecurityException;
import java.util.Objects;

public class Client {

  private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
  
  public void handleNotification(final String signatureHeader,
    final WebhookNotification notification, final String secret)
    throws GeneralSecurityException {
    String toSign = String.format("%s,%s", 
      notification.getObjectId(), notification.getTransactionId());
    String signature = calculateRFC2104HMAC(toSign, secret);
    if (!Objects.equals(signature, signatureHeader)) {
      throw new SecurityException(
        "Signature in header (" + signatureHeader + ") does not match expected signature (" + signature + ")");
    }
    ... // do whatever you need to do with this notification
  }

  private String calculateRFC2104HMAC(final String data, final String key) throws GeneralSecurityException {
    if (data == null || StringUtils.isBlank(key)) {
      return null;
    }
    Key signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
    Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
    mac.init(signingKey);
    return Hex.encodeHexString(mac.doFinal(data.getBytes()));
  }
  
  ...
}
<?php 

  function handleNotification($signatureHeader, $notification, $secret) {
    $signature = hash_hmac("sha1", 
         "$notification->objectId,$notification->transactionId", $secret);
    if($signature != $signatureHeader) {
      throw new \Exception("Signature in header ($signatureHeader) does not match expected signature ($signature)");
    }

    ... // do whatever you need to do with this notification
  }
...
?>
package "example"

import (
	"crypto/sha1"
	"crypto/hmac"
	"encoding/hex"
	"fmt"
)

func HandleNotification(signatureHeader string, message Notification, secret string) error {
	h := hmac.New(sha1.New, []byte(secret))
  msg := fmt.Sprintf("%s,%s", message.ObjectId, message.TransactionId)
	h.Write([]byte(msg))
	signature := hex.EncodeToString(h.Sum(nil))

	if signature != signatureHeader {
    return fmt.Errorf("Signature in header (%s) does not match expected signature (%s)", signatureHeader, signature)
	}

  ... // do whatever you need to do with this notification
	return nil
}
using System;
using System.Security.Cryptography;

  public void handleNotification(string signatureHeader, Notification notification, string secret) {
    secret = secret ?? "";

    var encoding = new System.Text.ASCIIEncoding();
    byte[] keyByte = encoding.GetBytes(secret);

    string toSign = String.Format("{0},{1}", notification.ObjectId, notification.TransactionId);
    byte[] messageBytes = encoding.GetBytes(toSign);

    using (var hmacsha256 = new HMACSHA1(keyByte))
    {
      byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
      string signature = BitConverter.ToString(hashmessage).Replace("-", string.Empty).ToLower();
      
      if (!signature.Equals(signatureHeader)) {
        throw new Exception(
          "Signature in header (" + signatureHeader + ") does not match expected signature (" + signature + ")");
      }
    }
    ... // do whatever you need to do with this notification
  }
...
import hmac
import hashlib

...
def handle_notification(signature_header, notification, secret):
	msg = "{},{}".format(notification["object_id"], notification["transaction_id"]).encode("utf-8")
	signature = hmac.new(secret, msg=msg, digestmod=hashlib.sha1).hexdigest()
	if signature_header != signature:
		ex_msg = "Signature in header ({}) does not match expected signature ({})".format(signature_header, signature)
		raise Exception(ex_msg)
  
  ... // do whatever you need to do with this notification

...

Available event flows

Event flows are basically groups of events. Here are the ones currently available and a small explanation about when they are triggered: