diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index fd80581003f0ae9d719ee5683ce0e7807fe72de1..363c9a424f9c9e92a1278ac54529d533516b6216 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -2218,6 +2218,19 @@ int ast_sip_create_request_with_auth(const struct ast_sip_auth_vector *auths, pj
  */
 struct ast_sip_endpoint *ast_sip_identify_endpoint(pjsip_rx_data *rdata);
 
+/*!
+ * \brief Get a specific header value from rdata
+ *
+ * \note The returned value does not need to be freed since it's from the rdata pool
+ *
+ * \param rdata The rdata
+ * \param str The header to find
+ *
+ * \retval NULL on failure
+ * \retval The header value on success
+ */
+char *ast_sip_rdata_get_header_value(pjsip_rx_data *rdata, const pj_str_t str);
+
 /*!
  * \brief Set the outbound proxy for an outbound SIP message
  *
diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h
index 48bfa00085ce2135259b69a4daee39ce928f948a..997054d4d7c20882a1db2a69def85d14e6be9b16 100644
--- a/include/asterisk/res_stir_shaken.h
+++ b/include/asterisk/res_stir_shaken.h
@@ -32,6 +32,13 @@ struct ast_stir_shaken_payload;
 
 struct ast_json;
 
+/*!
+ * \brief Retrieve the value for 'signature_timeout' from 'general' config object
+ *
+ * \retval The signature timeout
+ */
+unsigned int ast_stir_shaken_get_signature_timeout(void);
+
 /*!
  * \brief Add a STIR/SHAKEN verification result to a channel
  *
diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h
index 10dfe83c953d0e10dbfc4591a67d089742a19373..da14eb6e70480705d74f1179f941a2f14e2321dc 100644
--- a/include/asterisk/utils.h
+++ b/include/asterisk/utils.h
@@ -250,6 +250,19 @@ int ast_base64encode(char *dst, const unsigned char *src, int srclen, int max);
  */
 int ast_base64decode(unsigned char *dst, const char *src, int max);
 
+/*!
+ * \brief Same as ast_base64decode, but does the math for you and returns
+ * a decoded string
+ *
+ * \note The returned string will need to be freed later
+ *
+ * \param src The source buffer
+ *
+ * \retval NULL on failure
+ * \retval Decoded string on success
+ */
+char *ast_base64decode_string(const char *src);
+
 #define AST_URI_ALPHANUM     (1 << 0)
 #define AST_URI_MARK         (1 << 1)
 #define AST_URI_UNRESERVED   (AST_URI_ALPHANUM | AST_URI_MARK)
diff --git a/main/utils.c b/main/utils.c
index b45e7b51797093e6f99e74facf28d522281426d7..59880fde625cb1604a937298c5c1dc5a5d1aedf6 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -310,6 +310,37 @@ int ast_base64decode(unsigned char *dst, const char *src, int max)
 	return cnt;
 }
 
+/*! \brief Decode BASE64 encoded text and return the string */
+char *ast_base64decode_string(const char *src)
+{
+	size_t encoded_len;
+	size_t decoded_len;
+	int padding = 0;
+	unsigned char *decoded_string;
+
+	if (ast_strlen_zero(src)) {
+		return NULL;
+	}
+
+	encoded_len = strlen(src);
+	if (encoded_len > 2 && src[encoded_len - 1] == '=') {
+		padding++;
+		if (src[encoded_len - 2] == '=') {
+			padding++;
+		}
+	}
+
+	decoded_len = (encoded_len / 4 * 3) - padding;
+	decoded_string = ast_calloc(1, decoded_len);
+	if (!decoded_string) {
+		return NULL;
+	}
+
+	ast_base64decode(decoded_string, src, decoded_len);
+
+	return (char *)decoded_string;
+}
+
 /*! \brief encode text to BASE64 coding */
 int ast_base64encode_full(char *dst, const unsigned char *src, int srclen, int max, int linebreaks)
 {
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 659c631f69f5bf0ce634ab462e6416562d57fe16..3820243c08d08b6700b27598ddc887b5ffad8391 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -3183,6 +3183,21 @@ struct ast_sip_endpoint *ast_sip_identify_endpoint(pjsip_rx_data *rdata)
 	return endpoint;
 }
 
+char *ast_sip_rdata_get_header_value(pjsip_rx_data *rdata, const pj_str_t str)
+{
+	pjsip_generic_string_hdr *hdr;
+	pj_str_t hdr_val;
+
+	hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str, NULL);
+	if (!hdr) {
+		return NULL;
+	}
+
+	pj_strdup_with_null(rdata->tp_info.pool, &hdr_val, &hdr->hvalue);
+
+	return hdr_val.ptr;
+}
+
 static int do_cli_dump_endpt(void *v_a)
 {
 	struct ast_cli_args *a = v_a;
diff --git a/res/res_pjsip_stir_shaken.c b/res/res_pjsip_stir_shaken.c
index 702383c5a66646c0c57be36037ea8007154b4680..68665988dbba615276757242604b10f149c79ec8 100644
--- a/res/res_pjsip_stir_shaken.c
+++ b/res/res_pjsip_stir_shaken.c
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2020, Sangoma Technologies Corporation
  *
- * Kevin Harwell <kharwell@digium.com>
+ * Ben Ford <bford@sangoma.com>
  *
  * See http://www.asterisk.org for more information about
  * the Asterisk project. Please do not directly contact
@@ -18,22 +18,197 @@
 
 /*** MODULEINFO
 	<depend>crypto</depend>
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<depend>res_pjsip_session</depend>
+	<depend>res_stir_shaken</depend>
 	<support_level>core</support_level>
  ***/
 
 #include "asterisk.h"
 
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_session.h"
 #include "asterisk/module.h"
 
 #include "asterisk/res_stir_shaken.h"
 
+/*!
+ * \brief Get the attestation from the payload
+ *
+ * \param json_str The JSON string representation of the payload
+ *
+ * \retval Empty string on failure
+ * \retval The attestation on success
+ */
+static char *get_attestation_from_payload(const char *json_str)
+{
+	RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
+	char *attestation;
+
+	json = ast_json_load_string(json_str, NULL);
+	attestation = (char *)ast_json_string_get(ast_json_object_get(json, "attest"));
+
+	if (!ast_strlen_zero(attestation)) {
+		return attestation;
+	}
+
+	return "";
+}
+
+/*!
+ * \brief Compare the caller ID from the INVITE with the one in the payload
+ *
+ * \param json_str The JSON string represntation of the payload
+ *
+ * \retval -1 on failure
+ * \retval 0 on success
+ */
+static int compare_caller_id(char *caller_id, const char *json_str)
+{
+	RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
+	char *caller_id_other;
+
+	json = ast_json_load_string(json_str, NULL);
+	caller_id_other = (char *)ast_json_string_get(ast_json_object_get(
+		ast_json_object_get(json, "orig"), "tn"));
+
+	if (strcmp(caller_id, caller_id_other)) {
+		return -1;
+	}
+
+	return 0;
+}
+
+/*!
+ * \brief Compare the current timestamp with the one in the payload. If the difference
+ * is greater than the signature timeout, it's not valid anymore
+ *
+ * \param json_str The JSON string representation of the payload
+ *
+ * \retval -1 on failure
+ * \retval 0 on success
+ */
+static int compare_timestamp(const char *json_str)
+{
+	RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
+	long int timestamp;
+	struct timeval now = ast_tvnow();
+
+	json = ast_json_load_string(json_str, NULL);
+	timestamp = ast_json_integer_get(ast_json_object_get(json, "iat"));
+
+	if (now.tv_sec - timestamp > ast_stir_shaken_get_signature_timeout()) {
+		return -1;
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Session supplement callback on an incoming INVITE request
+ *
+ * When we receive an INVITE, check it for STIR/SHAKEN information and
+ * decide what to do from there
+ *
+ * \param session The session that has received an INVITE
+ * \param rdata The incoming INVITE
+ */
+static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+	static const pj_str_t identity_str = { "Identity", 8 };
+	char *identity_hdr_val;
+	char *encoded_val;
+	struct ast_channel *chan = session->channel;
+	char *caller_id = session->id.number.str;
+	RAII_VAR(char *, header, NULL, ast_free);
+	RAII_VAR(char *, payload, NULL, ast_free);
+	char *signature;
+	char *algorithm;
+	char *public_key_url;
+	char *attestation;
+	int mismatch = 0;
+	struct ast_stir_shaken_payload *ss_payload;
+
+	identity_hdr_val = ast_sip_rdata_get_header_value(rdata, identity_str);
+	if (ast_strlen_zero(identity_hdr_val)) {
+		ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_NOT_PRESENT);
+		return 0;
+	}
+
+	encoded_val = strtok_r(identity_hdr_val, ".", &identity_hdr_val);
+	header = ast_base64decode_string(encoded_val);
+	if (ast_strlen_zero(header)) {
+		ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);
+		return 0;
+	}
+
+	encoded_val = strtok_r(identity_hdr_val, ".", &identity_hdr_val);
+	payload = ast_base64decode_string(encoded_val);
+	if (ast_strlen_zero(payload)) {
+		ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);
+		return 0;
+	}
+
+	/* It's fine to leave the signature encoded */
+	signature = strtok_r(identity_hdr_val, ";", &identity_hdr_val);
+	if (ast_strlen_zero(signature)) {
+		ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);
+		return 0;
+	}
+
+	/* Trim "info=<" to get public key URL */
+	strtok_r(identity_hdr_val, "<", &identity_hdr_val);
+	public_key_url = strtok_r(identity_hdr_val, ">", &identity_hdr_val);
+	if (ast_strlen_zero(public_key_url)) {
+		ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);
+		return 0;
+	}
+
+	algorithm = strtok_r(identity_hdr_val, ";", &identity_hdr_val);
+	if (ast_strlen_zero(algorithm)) {
+		ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);
+		return 0;
+	}
+
+	attestation = get_attestation_from_payload(payload);
+
+	ss_payload = ast_stir_shaken_verify(header, payload, signature, algorithm, public_key_url);
+	if (!ss_payload) {
+		ast_stir_shaken_add_verification(chan, caller_id, attestation, AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);
+		return 0;
+	}
+	ast_stir_shaken_payload_free(ss_payload);
+
+	mismatch |= compare_caller_id(caller_id, payload);
+	mismatch |= compare_timestamp(payload);
+
+	if (mismatch) {
+		ast_stir_shaken_add_verification(chan, caller_id, attestation, AST_STIR_SHAKEN_VERIFY_MISMATCH);
+		return 0;
+	}
+
+	ast_stir_shaken_add_verification(chan, caller_id, attestation, AST_STIR_SHAKEN_VERIFY_PASSED);
+
+	return 0;
+}
+
+static struct ast_sip_session_supplement stir_shaken_supplement = {
+	.method = "INVITE",
+	.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL + 1, /* Run AFTER channel creation */
+	.incoming_request = stir_shaken_incoming_request,
+};
+
 static int unload_module(void)
 {
+	ast_sip_session_unregister_supplement(&stir_shaken_supplement);
 	return 0;
 }
 
 static int load_module(void)
 {
+	ast_sip_session_register_supplement(&stir_shaken_supplement);
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c
index 86117cdb6527945425c06c65fd7b41588352e633..5183c7e957c2f4bb67d546bc3c3010f66a147deb 100644
--- a/res/res_stir_shaken.c
+++ b/res/res_stir_shaken.c
@@ -65,6 +65,9 @@
 				<configOption name="curl_timeout" default="2">
 					<synopsis>Maximum time to wait to CURL certificates</synopsis>
 				</configOption>
+				<configOption name="signature_timeout" default="15">
+					<synopsis>Amount of time a signature is valid for</synopsis>
+				</configOption>
 			</configObject>
 			<configObject name="store">
 				<synopsis>STIR/SHAKEN certificate store options</synopsis>
@@ -181,6 +184,11 @@ void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *payload)
 	ast_free(payload);
 }
 
+unsigned int ast_stir_shaken_get_signature_timeout(void)
+{
+	return ast_stir_shaken_signature_timeout(stir_shaken_general_get());
+}
+
 /*!
  * \brief Convert an ast_stir_shaken_verification_result to string representation
  *
@@ -270,8 +278,8 @@ int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *ident
 		return -1;
 	}
 
-	if (ast_strlen_zero(attestation)) {
-		ast_log(LOG_ERROR, "No attestation to add STIR/SHAKEN verification to "
+	if (!attestation) {
+		ast_log(LOG_ERROR, "Attestation cannot be NULL to add STIR/SHAKEN verification to "
 			"channel %s\n", chan_name);
 		return -1;
 	}
@@ -593,8 +601,9 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const
 	EVP_PKEY *public_key;
 	char *filename;
 	int curl = 0;
-	struct ast_json_error err;
 	RAII_VAR(char *, file_path, NULL, ast_free);
+	RAII_VAR(char *, combined_str, NULL, ast_free);
+	size_t combined_size;
 
 	if (ast_strlen_zero(header)) {
 		ast_log(LOG_ERROR, "'header' is required for STIR/SHAKEN verification\n");
@@ -697,7 +706,16 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const
 		}
 	}
 
-	if (stir_shaken_verify_signature(payload, signature, public_key)) {
+	/* Combine the header and payload to get the original signed message: header.payload */
+	combined_size = strlen(header) + strlen(payload) + 2;
+	combined_str = ast_calloc(1, combined_size);
+	if (!combined_str) {
+		ast_log(LOG_ERROR, "Failed to allocate space for message to verify\n");
+		EVP_PKEY_free(public_key);
+		return NULL;
+	}
+	snprintf(combined_str, combined_size, "%s.%s", header, payload);
+	if (stir_shaken_verify_signature(combined_str, signature, public_key)) {
 		ast_log(LOG_ERROR, "Failed to verify signature\n");
 		EVP_PKEY_free(public_key);
 		return NULL;
@@ -712,14 +730,14 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const
 		return NULL;
 	}
 
-	ret_payload->header = ast_json_load_string(header, &err);
+	ret_payload->header = ast_json_load_string(header, NULL);
 	if (!ret_payload->header) {
 		ast_log(LOG_ERROR, "Failed to create JSON from header\n");
 		ast_stir_shaken_payload_free(ret_payload);
 		return NULL;
 	}
 
-	ret_payload->payload = ast_json_load_string(payload, &err);
+	ret_payload->payload = ast_json_load_string(payload, NULL);
 	if (!ret_payload->payload) {
 		ast_log(LOG_ERROR, "Failed to create JSON from payload\n");
 		ast_stir_shaken_payload_free(ret_payload);
@@ -1000,14 +1018,18 @@ static int stir_shaken_add_iat(struct ast_json *json)
 
 struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json)
 {
-	struct ast_stir_shaken_payload *payload;
+	struct ast_stir_shaken_payload *ss_payload;
 	unsigned char *signature;
 	const char *caller_id_num;
-	char *json_str = NULL;
+	const char *header;
+	const char *payload;
+	struct ast_json *tmp_json;
+	char *msg = NULL;
+	size_t msg_len;
 	struct stir_shaken_certificate *cert = NULL;
 
-	payload = stir_shaken_verify_json(json);
-	if (!payload) {
+	ss_payload = stir_shaken_verify_json(json);
+	if (!ss_payload) {
 		return NULL;
 	}
 
@@ -1052,27 +1074,34 @@ struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json)
 		goto cleanup;
 	}
 
-	json_str = ast_json_dump_string(json);
-	if (!json_str) {
-		ast_log(LOG_ERROR, "Failed to convert JSON to string\n");
+	/* Get the header and the payload. Combine them to get the message to sign */
+	tmp_json = ast_json_object_get(json, "header");
+	header = ast_json_dump_string(tmp_json);
+	tmp_json = ast_json_object_get(json, "payload");
+	payload = ast_json_dump_string(tmp_json);
+	msg_len = strlen(header) + strlen(payload) + 2;
+	msg = ast_calloc(1, msg_len);
+	if (!msg) {
+		ast_log(LOG_ERROR, "Failed to allocate space for message to sign\n");
 		goto cleanup;
 	}
+	snprintf(msg, msg_len, "%s.%s", header, payload);
 
-	signature = stir_shaken_sign(json_str, stir_shaken_certificate_get_private_key(cert));
+	signature = stir_shaken_sign(msg, stir_shaken_certificate_get_private_key(cert));
 	if (!signature) {
 		goto cleanup;
 	}
 
-	payload->signature = signature;
+	ss_payload->signature = signature;
 	ao2_cleanup(cert);
-	ast_json_free(json_str);
+	ast_free(msg);
 
-	return payload;
+	return ss_payload;
 
 cleanup:
 	ao2_cleanup(cert);
-	ast_stir_shaken_payload_free(payload);
-	ast_json_free(json_str);
+	ast_stir_shaken_payload_free(ss_payload);
+	ast_free(msg);
 	return NULL;
 }
 
@@ -1424,12 +1453,13 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 {
 	char *caller_id_number = "1234567";
 	char *public_key_url = "http://testing123";
-	char *header = "{\"header\": \"placeholder\"}";
+	char *header;
+	char *payload;
+	struct ast_json *tmp_json;
 	char public_path[] = "/tmp/stir_shaken_public.XXXXXX";
 	char private_path[] = "/tmp/stir_shaken_public.XXXXXX";
 	RAII_VAR(char *, rm_on_exit_public, public_path, unlink);
 	RAII_VAR(char *, rm_on_exit_private, private_path, unlink);
-	RAII_VAR(char *, json_str, NULL, ast_json_free);
 	RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
 	RAII_VAR(struct ast_stir_shaken_payload *, signed_payload, NULL, ast_stir_shaken_payload_free);
 	RAII_VAR(struct ast_stir_shaken_payload *, returned_payload, NULL, ast_stir_shaken_payload_free);
@@ -1463,16 +1493,14 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 		return AST_TEST_FAIL;
 	}
 
-	/* Get the message to use for verification */
-	json_str = ast_json_dump_string(json);
-	if (!json_str) {
-		ast_test_status_update(test, "Failed to create string from JSON\n");
-		test_stir_shaken_cleanup_cert(caller_id_number);
-		return AST_TEST_FAIL;
-	}
+	/* Get the header and payload for ast_stir_shaken_verify */
+	tmp_json = ast_json_object_get(json, "header");
+	header = ast_json_dump_string(tmp_json);
+	tmp_json = ast_json_object_get(json, "payload");
+	payload = ast_json_dump_string(tmp_json);
 
 	/* Test empty header parameter */
-	returned_payload = ast_stir_shaken_verify("", json_str, (const char *)signed_payload->signature,
+	returned_payload = ast_stir_shaken_verify("", payload, (const char *)signed_payload->signature,
 		STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
 	if (returned_payload) {
 		ast_test_status_update(test, "Verified a signature with missing 'header'\n");
@@ -1490,7 +1518,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 	}
 
 	/* Test empty signature parameter */
-	returned_payload = ast_stir_shaken_verify(header, json_str, "",
+	returned_payload = ast_stir_shaken_verify(header, payload, "",
 		STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
 	if (returned_payload) {
 		ast_test_status_update(test, "Verified a signature with missing 'signature'\n");
@@ -1499,7 +1527,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 	}
 
 	/* Test empty algorithm parameter */
-	returned_payload = ast_stir_shaken_verify(header, json_str, (const char *)signed_payload->signature,
+	returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature,
 		"", public_key_url);
 	if (returned_payload) {
 		ast_test_status_update(test, "Verified a signature with missing 'algorithm'\n");
@@ -1508,7 +1536,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 	}
 
 	/* Test empty public key URL */
-	returned_payload = ast_stir_shaken_verify(header, json_str, (const char *)signed_payload->signature,
+	returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature,
 		STIR_SHAKEN_ENCRYPTION_ALGORITHM, "");
 	if (returned_payload) {
 		ast_test_status_update(test, "Verified a signature with missing 'public key URL'\n");
@@ -1520,7 +1548,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 	test_stir_shaken_add_fake_astdb_entry(public_key_url, public_path);
 
 	/* Verify a valid signature */
-	returned_payload = ast_stir_shaken_verify(header, json_str, (const char *)signed_payload->signature,
+	returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature,
 		STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url);
 	if (!returned_payload) {
 		ast_test_status_update(test, "Failed to verify a valid signature\n");
diff --git a/res/res_stir_shaken.exports.in b/res/res_stir_shaken.exports.in
new file mode 100644
index 0000000000000000000000000000000000000000..10d214f914912d8f6bfaf12de626f7d8018ab158
--- /dev/null
+++ b/res/res_stir_shaken.exports.in
@@ -0,0 +1,6 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXast_stir_*;
+	local:
+		*;
+};
diff --git a/res/res_stir_shaken/general.c b/res/res_stir_shaken/general.c
index edf8f85dd072f75e55b8513a5f4c6234196670af..d241082411096cb1058e2f068d78c520f8af2991 100644
--- a/res/res_stir_shaken/general.c
+++ b/res/res_stir_shaken/general.c
@@ -31,6 +31,7 @@
 #define DEFAULT_CA_PATH ""
 #define DEFAULT_CACHE_MAX_SIZE 1000
 #define DEFAULT_CURL_TIMEOUT 2
+#define DEFAULT_SIGNATURE_TIMEOUT 15
 
 struct stir_shaken_general {
 	SORCERY_OBJECT(details);
@@ -44,6 +45,8 @@ struct stir_shaken_general {
 	unsigned int cache_max_size;
 	/*! Maximum time to wait to CURL certificates */
 	unsigned int curl_timeout;
+	/*! Amount of time a signature is valid for */
+	unsigned int signature_timeout;
 };
 
 static struct stir_shaken_general *default_config = NULL;
@@ -86,6 +89,11 @@ unsigned int ast_stir_shaken_curl_timeout(const struct stir_shaken_general *cfg)
 	return cfg ? cfg->curl_timeout : DEFAULT_CURL_TIMEOUT;
 }
 
+unsigned int ast_stir_shaken_signature_timeout(const struct stir_shaken_general *cfg)
+{
+	return cfg ? cfg->signature_timeout : DEFAULT_SIGNATURE_TIMEOUT;
+}
+
 static void stir_shaken_general_destructor(void *obj)
 {
 	struct stir_shaken_general *cfg = obj;
@@ -261,6 +269,9 @@ int stir_shaken_general_load(void)
 	ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "curl_timeout",
 		__stringify(DEFAULT_CURL_TIMEOUT), OPT_UINT_T, 0,
 		FLDSET(struct stir_shaken_general, curl_timeout));
+	ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "signature_timeout",
+		__stringify(DEFAULT_SIGNATURE_TIMEOUT), OPT_UINT_T, 0,
+		FLDSET(struct stir_shaken_general, signature_timeout));
 
 	if (ast_sorcery_instance_observer_add(sorcery, &stir_shaken_general_observer)) {
 		ast_log(LOG_ERROR, "stir/shaken - failed to register loaded observer for '%s' "
diff --git a/res/res_stir_shaken/general.h b/res/res_stir_shaken/general.h
index 357933b82ab08a3cf7d267916fe3ea25b9266a6c..3ea1d693f450c0b35091520c69a0a6305f43bd50 100644
--- a/res/res_stir_shaken/general.h
+++ b/res/res_stir_shaken/general.h
@@ -83,6 +83,17 @@ unsigned int ast_stir_shaken_cache_max_size(const struct stir_shaken_general *cf
  */
 unsigned int ast_stir_shaken_curl_timeout(const struct stir_shaken_general *cfg);
 
+/*!
+ * \brief Retrieve the 'signature_timeout' general configuration option value
+ *
+ * \note if a NULL configuration is given, then the default value is returned
+ *
+ * \param cfg A 'general' configuration object
+ *
+ * \retval The 'signature_timeout' value
+ */
+unsigned int ast_stir_shaken_signature_timeout(const struct stir_shaken_general *cfg);
+
 /*!
  * \brief Load time initialization for the stir/shaken 'general' configuration
  *