diff --git a/configs/samples/stir_shaken.conf.sample b/configs/samples/stir_shaken.conf.sample
index 57d1634057d224e47bca1ed14fd146f25ece3f97..71acad23c4d3d2c700b0579230b25728d9824660 100644
--- a/configs/samples/stir_shaken.conf.sample
+++ b/configs/samples/stir_shaken.conf.sample
@@ -47,3 +47,9 @@
 ;
 ; URL to the public key
 ;public_key_url=http://mycompany.com/alice.pub
+;
+; Must have an attestation of A, B, or C
+;attestation=C
+;
+; The origination identifier for the certificate
+;origid=MyAsterisk
diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h
index 997054d4d7c20882a1db2a69def85d14e6be9b16..cad9282fc439f697da6aa47b81c1a78199389b9b 100644
--- a/include/asterisk/res_stir_shaken.h
+++ b/include/asterisk/res_stir_shaken.h
@@ -21,6 +21,10 @@
 #include <openssl/evp.h>
 #include <openssl/pem.h>
 
+#define STIR_SHAKEN_ENCRYPTION_ALGORITHM "ES256"
+#define STIR_SHAKEN_PPT "shaken"
+#define STIR_SHAKEN_TYPE "passport"
+
 enum ast_stir_shaken_verification_result {
 	AST_STIR_SHAKEN_VERIFY_NOT_PRESENT, /*! No STIR/SHAKEN information was available */
 	AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED, /*! Signature verification failed */
@@ -32,6 +36,24 @@ struct ast_stir_shaken_payload;
 
 struct ast_json;
 
+/*!
+ * \brief Retrieve the value for 'signature' from an ast_stir_shaken_payload
+ *
+ * \param payload The payload
+ *
+ * \retval The signature
+ */
+unsigned char *ast_stir_shaken_payload_get_signature(const struct ast_stir_shaken_payload *payload);
+
+/*!
+ * \brief Retrieve the value for 'public_key_url' from an ast_stir_shaken_payload
+ *
+ * \param payload The payload
+ *
+ * \retval The public key URL
+ */
+char *ast_stir_shaken_payload_get_public_key_url(const struct ast_stir_shaken_payload *payload);
+
 /*!
  * \brief Retrieve the value for 'signature_timeout' from 'general' config object
  *
diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h
index da14eb6e70480705d74f1179f941a2f14e2321dc..f6280ebdfe5404c6e67bc008ce27f3fd1eaf1ab0 100644
--- a/include/asterisk/utils.h
+++ b/include/asterisk/utils.h
@@ -239,6 +239,19 @@ int ast_base64encode_full(char *dst, const unsigned char *src, int srclen, int m
  */
 int ast_base64encode(char *dst, const unsigned char *src, int srclen, int max);
 
+/*!
+ * \brief Same as ast_base64encode, but does hte math for you and returns
+ * an encoded string
+ *
+ * \note The returned string will need to be freed later
+ *
+ * \param src The source buffer
+ *
+ * \retval NULL on failure
+ * \retval Encoded string on success
+ */
+char *ast_base64encode_string(const char *src);
+
 /*!
  * \brief Decode data from base64
  * \param dst the destination buffer
diff --git a/main/utils.c b/main/utils.c
index 59880fde625cb1604a937298c5c1dc5a5d1aedf6..0b6c649342cc802841af018365fff4403f124531 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -398,6 +398,24 @@ int ast_base64encode(char *dst, const unsigned char *src, int srclen, int max)
 	return ast_base64encode_full(dst, src, srclen, max, 0);
 }
 
+/*! \brief Encode to BASE64 and return encoded string */
+char *ast_base64encode_string(const char *src)
+{
+	size_t encoded_len;
+	char *encoded_string;
+
+	if (ast_strlen_zero(src)) {
+		return NULL;
+	}
+
+	encoded_len = ((strlen(src) * 4 / 3 + 3) & ~3) + 1;
+	encoded_string = ast_calloc(1, encoded_len);
+
+	ast_base64encode(encoded_string, (const unsigned char *)src, strlen(src), encoded_len);
+
+	return encoded_string;
+}
+
 static void base64_init(void)
 {
 	int x;
diff --git a/res/res_pjsip_stir_shaken.c b/res/res_pjsip_stir_shaken.c
index 68665988dbba615276757242604b10f149c79ec8..3620579d84b6a650985bcbc994679871935161cd 100644
--- a/res/res_pjsip_stir_shaken.c
+++ b/res/res_pjsip_stir_shaken.c
@@ -194,10 +194,102 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r
 	return 0;
 }
 
+static void add_identity_header(const struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+	static const pj_str_t identity_str = { "Identity", 8 };
+	pjsip_generic_string_hdr *identity_hdr;
+	pj_str_t identity_val;
+	pjsip_fromto_hdr *old_identity;
+	char *signature;
+	char *public_key_url;
+	struct ast_json *header;
+	struct ast_json *payload;
+	char *dumped_string;
+	RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
+	RAII_VAR(struct ast_stir_shaken_payload *, ss_payload, NULL, ast_stir_shaken_payload_free);
+	RAII_VAR(char *, encoded_header, NULL, ast_free);
+	RAII_VAR(char *, encoded_payload, NULL, ast_free);
+	RAII_VAR(char *, combined_str, NULL, ast_free);
+	size_t combined_size;
+
+	old_identity = pjsip_msg_find_hdr_by_name(tdata->msg, &identity_str, NULL);
+	if (old_identity) {
+		return;
+	}
+
+	/* x5u (public key URL), attestation, and origid will be added by ast_stir_shaken_sign */
+	json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", "ES256", "ppt", "shaken", "typ", "passport",
+		"payload", "orig", "tn", session->id.number.str);
+	if (!json) {
+		ast_log(LOG_ERROR, "Failed to allocate memory for STIR/SHAKEN JSON\n");
+		return;
+	}
+
+	ss_payload = ast_stir_shaken_sign(json);
+	if (!ss_payload) {
+		ast_log(LOG_ERROR, "Failed to allocate memory for STIR/SHAKEN payload\n");
+		return;
+	}
+
+	header = ast_json_object_get(json, "header");
+	dumped_string = ast_json_dump_string(header);
+	encoded_header = ast_base64encode_string(dumped_string);
+	ast_json_free(dumped_string);
+	if (!encoded_header) {
+		ast_log(LOG_ERROR, "Failed to encode STIR/SHAKEN header\n");
+		return;
+	}
+
+	payload = ast_json_object_get(json, "payload");
+	dumped_string = ast_json_dump_string(payload);
+	encoded_payload = ast_base64encode_string(dumped_string);
+	ast_json_free(dumped_string);
+	if (!encoded_payload) {
+		ast_log(LOG_ERROR, "Failed to encode STIR/SHAKEN payload\n");
+		return;
+	}
+
+	signature = (char *)ast_stir_shaken_payload_get_signature(ss_payload);
+	public_key_url = ast_stir_shaken_payload_get_public_key_url(ss_payload);
+
+	/* The format for the identity header:
+	 * header.payload.signature;info=<public_key_url>alg=STIR_SHAKEN_ENCRYPTION_ALGORITHM;ppt=STIR_SHAKEN_PPT
+	 */
+	combined_size = strlen(encoded_header) + 1 + strlen(encoded_payload) + 1
+		+ strlen(signature) + strlen(";info=<>alg=;ppt=") + strlen(public_key_url)
+		+ strlen(STIR_SHAKEN_ENCRYPTION_ALGORITHM) + strlen(STIR_SHAKEN_PPT) + 1;
+	combined_str = ast_calloc(1, combined_size);
+	if (!combined_str) {
+		ast_log(LOG_ERROR, "Failed to allocate memory for STIR/SHAKEN identity string\n");
+		return;
+	}
+	snprintf(combined_str, combined_size, "%s.%s.%s;info=<%s>alg=%s;ppt=%s", encoded_header,
+		encoded_payload, signature, public_key_url, STIR_SHAKEN_ENCRYPTION_ALGORITHM, STIR_SHAKEN_PPT);
+
+	identity_val = pj_str(combined_str);
+	identity_hdr = pjsip_generic_string_hdr_create(tdata->pool, &identity_str, &identity_val);
+	if (!identity_hdr) {
+		ast_log(LOG_ERROR, "Failed to create STIR/SHAKEN Identity header\n");
+		return;
+	}
+
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)identity_hdr);
+}
+
+static void stir_shaken_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+	if (ast_strlen_zero(session->id.number.str) && session->id.number.valid) {
+		return;
+	}
+
+	add_identity_header(session, tdata);
+}
+
 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,
+	.outgoing_request = stir_shaken_outgoing_request,
 };
 
 static int unload_module(void)
diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c
index 5183c7e957c2f4bb67d546bc3c3010f66a147deb..632fd1b38f13bb9746288520623cd80ff904f8f8 100644
--- a/res/res_stir_shaken.c
+++ b/res/res_stir_shaken.c
@@ -99,6 +99,12 @@
 					 Must be a valid http, or https, URL.
 					</para></description>
 				</configOption>
+				<configOption name="attestation">
+					<synopsis>Attestation level</synopsis>
+				</configOption>
+				<configOption name="origid" default="">
+					<synopsis>The origination ID</synopsis>
+				</configOption>
 				<configOption name="caller_id_number" default="">
 					<synopsis>The caller ID number to match on.</synopsis>
 				</configOption>
@@ -136,10 +142,6 @@
 	</function>
  ***/
 
-#define STIR_SHAKEN_ENCRYPTION_ALGORITHM "ES256"
-#define STIR_SHAKEN_PPT "shaken"
-#define STIR_SHAKEN_TYPE "passport"
-
 static struct ast_sorcery *stir_shaken_sorcery;
 
 /* Used for AstDB entries */
@@ -184,6 +186,16 @@ void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *payload)
 	ast_free(payload);
 }
 
+unsigned char *ast_stir_shaken_payload_get_signature(const struct ast_stir_shaken_payload *payload)
+{
+	return payload ? payload->signature : NULL;
+}
+
+char *ast_stir_shaken_payload_get_public_key_url(const struct ast_stir_shaken_payload *payload)
+{
+	return payload ? payload->public_key_url : NULL;
+}
+
 unsigned int ast_stir_shaken_get_signature_timeout(void)
 {
 	return ast_stir_shaken_signature_timeout(stir_shaken_general_get());
@@ -1020,6 +1032,7 @@ struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json)
 {
 	struct ast_stir_shaken_payload *ss_payload;
 	unsigned char *signature;
+	const char *public_key_url;
 	const char *caller_id_num;
 	const char *header;
 	const char *payload;
@@ -1049,22 +1062,19 @@ struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json)
 		goto cleanup;
 	}
 
-	if (stir_shaken_add_x5u(json, stir_shaken_certificate_get_public_key_url(cert))) {
+	public_key_url = stir_shaken_certificate_get_public_key_url(cert);
+	if (stir_shaken_add_x5u(json, public_key_url)) {
 		ast_log(LOG_ERROR, "Failed to add 'x5u' (public key URL) to payload\n");
 		goto cleanup;
 	}
+	ss_payload->public_key_url = ast_strdup(public_key_url);
 
-	/* TODO: This is just a placeholder for adding 'attest', 'iat', and
-	 * 'origid' to the payload. Later, additional logic will need to be
-	 * added to determine what these values actually are, but the functions
-	 * themselves are ready to go.
-	 */
-	if (stir_shaken_add_attest(json, "B")) {
+	if (stir_shaken_add_attest(json, stir_shaken_certificate_get_attestation(cert))) {
 		ast_log(LOG_ERROR, "Failed to add 'attest' to payload\n");
 		goto cleanup;
 	}
 
-	if (stir_shaken_add_origid(json, "asterisk")) {
+	if (stir_shaken_add_origid(json, stir_shaken_certificate_get_origid(cert))) {
 		ast_log(LOG_ERROR, "Failed to add 'origid' to payload\n");
 		goto cleanup;
 	}
diff --git a/res/res_stir_shaken/certificate.c b/res/res_stir_shaken/certificate.c
index 73b5ce107cf9578e23effe3f292af9bf79b8d0e8..1a1447e5acdf4eb208a4066d7ae1253beb1ba583 100644
--- a/res/res_stir_shaken/certificate.c
+++ b/res/res_stir_shaken/certificate.c
@@ -38,6 +38,10 @@ struct stir_shaken_certificate {
 		AST_STRING_FIELD(public_key_url);
 		/*! The caller ID number associated with the certificate */
 		AST_STRING_FIELD(caller_id_number);
+		/*! The attestation level for this certificate */
+		AST_STRING_FIELD(attestation);
+		/*! The origination ID for this certificate */
+		AST_STRING_FIELD(origid);
 	);
 	/*! The private key for the certificate */
 	EVP_PKEY *private_key;
@@ -93,20 +97,22 @@ struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(
 
 const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert)
 {
-	if (!cert) {
-		return NULL;
-	}
+	return cert ? cert->public_key_url : NULL;
+}
 
-	return cert->public_key_url;
+const char *stir_shaken_certificate_get_attestation(struct stir_shaken_certificate *cert)
+{
+	return cert ? cert->attestation : NULL;
 }
 
-EVP_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert)
+const char *stir_shaken_certificate_get_origid(struct stir_shaken_certificate *cert)
 {
-	if (!cert) {
-		return NULL;
-	}
+	return cert ? cert->origid : NULL;
+}
 
-	return cert->private_key;
+EVP_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert)
+{
+	return cert ? cert->private_key : NULL;
 }
 
 static int stir_shaken_certificate_apply(const struct ast_sorcery *sorcery, void *obj)
@@ -114,11 +120,16 @@ static int stir_shaken_certificate_apply(const struct ast_sorcery *sorcery, void
 	EVP_PKEY *private_key;
 	struct stir_shaken_certificate *cert = obj;
 
-	if (strlen(cert->caller_id_number) == 0) {
+	if (ast_strlen_zero(cert->caller_id_number)) {
 		ast_log(LOG_ERROR, "Caller ID must be present\n");
 		return -1;
 	}
 
+	if (ast_strlen_zero(cert->attestation)) {
+		ast_log(LOG_ERROR, "Attestation must be present\n");
+		return -1;
+	}
+
 	private_key = stir_shaken_read_key(cert->path, 1);
 	if (!private_key) {
 		return -1;
@@ -244,6 +255,28 @@ static int public_key_url_to_str(const void *obj, const intptr_t *args, char **b
 	return 0;
 }
 
+static int on_load_attestation(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct stir_shaken_certificate *cfg = obj;
+
+	if (strcmp(var->value, "A") && strcmp(var->value, "B") && strcmp(var->value, "C")) {
+		ast_log(LOG_ERROR, "stir/shaken - attestation level must be A, B, or C (object=%s)\n",
+			ast_sorcery_object_get_id(cfg));
+		return -1;
+	}
+
+	return ast_string_field_set(cfg, attestation, var->value);
+}
+
+static int attestation_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct stir_shaken_certificate *cfg = obj;
+
+	*buf = ast_strdup(cfg->attestation);
+
+	return 0;
+}
+
 #ifdef TEST_FRAMEWORK
 
 /* Name for test certificaate */
@@ -343,6 +376,9 @@ int stir_shaken_certificate_load(void)
 		on_load_path, path_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "public_key_url", "",
 		on_load_public_key_url, public_key_url_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "attestation", "",
+		on_load_attestation, attestation_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "origid", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct stir_shaken_certificate, origid));
 	ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "caller_id_number", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct stir_shaken_certificate, caller_id_number));
 
 	ast_cli_register_multiple(stir_shaken_certificate_cli,
diff --git a/res/res_stir_shaken/certificate.h b/res/res_stir_shaken/certificate.h
index ff303180bff560329b90734bab87490103aabb18..6eeb36bec82d41523b5d4492b994efee93354912 100644
--- a/res/res_stir_shaken/certificate.h
+++ b/res/res_stir_shaken/certificate.h
@@ -44,6 +44,26 @@ struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(
  */
 const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert);
 
+/*!
+ * \brief Get the attestation level associated with a certificate
+ *
+ * \param cert The certificate
+ *
+ * \retval NULL on failure
+ * \retval The attestation on success
+ */
+const char *stir_shaken_certificate_get_attestation(struct stir_shaken_certificate *cert);
+
+/*!
+ * \brief Get the origination ID associated with a certificate
+ *
+ * \param cert The certificate
+ *
+ * \retval NULL on failure
+ * \retval The origid on success
+ */
+const char *stir_shaken_certificate_get_origid(struct stir_shaken_certificate *cert);
+
 /*!
  * \brief Get the private key associated with a certificate
  *