Events API
Webhooks
13min
events are exposed and dispatched through our webhooks service where the vendor platform user can configure an endpoint listener and a list of events happening in your licensespring account which will be dispatched to that endpoint url https is used to send webhook events to your app as a json payload this is enterprise feature only for more information, please contact sales setup webhooks can be configured in the vendor platform under settings > webhooks to start receiving webhook events in your app you need to create and register a webhook endpoint using the webhooks dashboard set up an https endpoint function that can accept webhook requests so that it handles post requests with a json payload consisting of an event object webhook configuration panel along with configuration view, the webhooks panel will display history of sent events and option to rollover the secret key needed to verify the webhook data authenticity webhook details view instructions handle duplicate events webhook endpoints might occasionally receive the same event more than once you can guard against duplicated event receipts by making your event processing idempotent one way of doing this is logging the events you’ve processed, and then not processing already logged events only listen to event types your integration requires configure your webhook endpoints to receive only the types of events required by your integration listening for extra events (or all events) puts undue strain on your server and we don’t recommend it receive events with an https server if you use an https url for your webhook endpoint, licensespring validates that the connection to your server is secure before sending your webhook data for this to work, your server must be correctly configured to support https with a valid server certificate roll endpoint signing secrets periodically the secret key used for verifying that events come from licensespring is rollable you can choose to immediately expire the current secret key or delay its expiration for up to 24 hours to allow yourself time to update the verification code on your server during this time, multiple secrets are active for the endpoint licensespring generates one signature per secret until expiration to keep them safe, we recommend that you roll secret keys periodically, or when you suspect a compromised secret key verify events are sent from licensespring verify webhook signatures to confirm that received events are sent from licensespring licensespring signs webhook events it sends to your endpoints by including a signature in each event’s licensespring signature header this allows you to verify that the events were sent by licensespring, not by a third party you can verify signatures either using our official libraries, or verify manually using your own solution the secret key for each endpoint can be found in the webhooks dashboard verifying signatures using our official sdks at the moment, verification of a webhook signature is included in python sdk only you perform the verification by providing the event payload, the licensespring signature header, and the endpoint’s secret if verification fails, you get an error licensespring requires the raw body of the request to perform signature verification if you’re using a framework, make sure it doesn’t manipulate the raw body any manipulation to the raw body of the request causes the verification to fail from licensespring webhook import webhook from licensespring webhook error import signatureverificationerror from flask import flask, request \# using flask \# signing key is set as licensespring signing secret env var @app route('/licensespring/webhooks/', methods=\['post']) def licensespring webhooks() licensespring signature = request headers get('licensespring signature') try event = webhook get event( payload=request get data(as text=true), sig header=licensespring signature, secret=os environ\['licensespring signing secret']) except signatureverificationerror return ('signature is not valid ', 400) \# handle the event print('received event {} ({}) from ls with data {}' format( event get('event'), event get('id'), event get('data') )) return 'webhook processed ' verifying signatures manually the licensespring signature header included in each signed event contains a timestamp and one or more signatures that you must verify the timestamp is prefixed by t=, and each signature is prefixed by a scheme schemes start with v, followed by an integer currently, the only valid live signature scheme is v1 t=1706880193,v1=b5125c98c3b58e09bdbe3615057ee2b22f421e67d9ee5791cc7d2d43bfd6363f licensespring generates signatures using a hash based message authentication code (hmac) with sha 256 to prevent downgrade attacks, ignore all schemes that are not v1 you can have multiple signatures with the same scheme secret pair when you roll an endpoint’s secret, and keep the previous secret active for up to 24 hours during this time, your endpoint has multiple active secrets and licensespring generates one signature for each secret to create a manual solution for verifying signatures, you must complete the following steps step 1 extract the timestamp and signatures from the header split the header using the , character as the separator to get a list of elements then split each element using the = character as the separator to get a prefix and value pair the value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature (or signatures) you can discard all other elements step 2 prepare the string the signed payload string is created by concatenating the timestamp (as a string) the character the actual json payload (that is, the request body) step 3 determine the expected signature compute an hmac with the sha256 hash function use the endpoint’s signing secret as the key, and use the signed payload string as the message step 4 compare the signatures compare the signature (or signatures) in the header to the expected signature for an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance import hmac import json import time from hashlib import sha256 def secure compare(val1, val2) return hmac compare digest(val1, val2) class signatureverificationerror(exception) def init (self, message, sig header, payload) self message = message self sig header = sig header self payload = payload def str (self) return self message def repr (self) return str(self) class webhooksignature expected scheme = "v1" @staticmethod def compute signature(payload, secret) mac = hmac new( secret encode("utf 8"), msg=payload encode("utf 8"), digestmod=sha256, ) return mac hexdigest() @staticmethod def get timestamp and signatures(header, scheme) list items = \[i split("=", 2) for i in header split(",")] timestamp = int(\[i\[1] for i in list items if i\[0] == "t"]\[0]) signatures = \[i\[1] for i in list items if i\[0] == scheme] return timestamp, signatures @classmethod def verify header(cls, payload, header, secret, tolerance=none) try timestamp, signatures = cls get timestamp and signatures( header, cls expected scheme ) except exception raise signatureverificationerror( "unable to extract timestamp and signatures from header", header, payload, ) if not signatures raise signatureverificationerror( "no signatures found with expected scheme {}" format( cls expected scheme ), header, payload, ) signed payload = "%d %s" % (timestamp, payload) expected sig = cls compute signature(signed payload, secret) if not any(secure compare(expected sig, s) for s in signatures) raise signatureverificationerror( "no signatures found matching the expected signature for payload", header, payload, ) if tolerance and timestamp < time time() tolerance raise signatureverificationerror( "timestamp outside the tolerance zone ({})" format(timestamp), header, payload, ) return true class webhook default tolerance = 300 # 5 minutes @staticmethod def get event(payload, sig header, secret, tolerance=default tolerance) webhooksignature verify header(payload, sig header, secret, tolerance) return json loads(payload) event = webhook get event( payload= payload goes here , sig header= licensespring signature header goes here , secret= signing key goes here , tolerence= optional tolerence goes here , )const crypto = require('node\ crypto'); const hmacsha256 = (key, data) => crypto createhmac('sha256', key) update(data) digest('hex'); function securecompare(val1, val2) { try { return crypto timingsafeequal(buffer from(val1), buffer from(val2)); } catch (e) { // handles the case where the inputs are of different lengths return false; } } function computesignature(payload, secret) { return hmacsha256(secret, payload); } function gettimestampandsignatures(header, scheme) { const listitems = header split(',') map(i => i split('=')); const timestamp = parseint(listitems find(i => i\[0] === 't')\[1], 10); const signatures = listitems filter(i => i\[0] === scheme) map(i => i\[1]); return { timestamp, signatures }; } function verifyheader(payload, header, secret, tolerance = 300) { // default tolerance of 5 minutes let timestamp, signatures; try { ({ timestamp, signatures } = gettimestampandsignatures(header, 'v1')); } catch (error) { throw new error('unable to extract timestamp and signatures from header'); } if (!signatures length) { throw new error('no signatures found with expected scheme v1'); } const signedpayload = `${timestamp} ${payload}`; const expectedsig = computesignature(signedpayload, secret); const issignaturevalid = signatures some(sig => securecompare(expectedsig, sig)); if (!issignaturevalid) { throw new error('no signatures found matching the expected signature for payload'); } if (tolerance && timestamp < (date now() / 1000) tolerance) { throw new error(`timestamp outside the tolerance zone (${timestamp})`); } return true; } function getevent(payload, sigheader, secret, tolerance = 300) { verifyheader(payload, sigheader, secret, tolerance); return json parse(payload); } // usage example try { const event = getevent( payload, // payload goes here sigheader, // licensespring signature header goes here secret, // signing key goes here tolerance // optional tolerance goes here ); // process event } catch (error) { console error(error message); } preventing replay attacks a replay attack is when an attacker intercepts a valid payload and its signature, then re transmits them to mitigate such attacks, licensespring includes a timestamp in the licensespring signature header because this timestamp is part of the signed payload, it’s also verified by the signature, so an attacker can’t change the timestamp without invalidating the signature if the signature is valid but the timestamp is too old, you can have your application reject the payload our sdks have a default tolerance of 5 minutes between the timestamp and the current time you can change this tolerance by providing an additional parameter when verifying signatures quickly return a 2xx response quickly return a successful status code (2xx) prior to any complex logic that could cause a timeout (timeout occurs after 20 seconds) in the event of a timeout the webhook will be logged with 408 request timeout status code