diff --git a/Makefile b/Makefile
index cefa712cda7524012c97004b48ddb1e62816fef6..2a216e1b47600ba21525b784257378a8271ba502 100644
--- a/Makefile
+++ b/Makefile
@@ -382,6 +382,14 @@ sc.db.rollback_n: ## run migrations
 	docker-compose -f docker-compose-sc.yml \
 	run -e SIGNALC_ENV=test --entrypoint 'gradle --console=plain rollbackCount -PliquibaseCommandValue=$(N)' signalc
 
+sc.db.clear_checksums: ## run migrations
+	echo "----- rolling back 1 development migration" && \
+	docker-compose -f docker-compose-sc.yml \
+	run -e SIGNALC_ENV=development --entrypoint 'gradle --console=plain clearCheckSums' signalc && \
+	echo "----- rolling back 1 test migration" && \
+	docker-compose -f docker-compose-sc.yml \
+	run -e SIGNALC_ENV=test --entrypoint 'gradle --console=plain clearCheckSums' signalc
+
 sc.db.psql: # get a psql shell on signalc db
 	./bin/sc/psql
 
diff --git a/signalc/build.gradle.kts b/signalc/build.gradle.kts
index 36deb76a02301d718e1cfde14e977d3fcc8626fc..0aa0f4af0407a9c005175b5fc7b0d5cb6bbb48a0 100644
--- a/signalc/build.gradle.kts
+++ b/signalc/build.gradle.kts
@@ -78,7 +78,7 @@ object Versions {
     const val kotlinSerialization = "1.2.1"
     const val h2 = "1.4.199"
     const val hikariCp = "4.0.3"
-    const val libsignal = "2.15.3_unofficial_t23_m1"
+    const val libsignal = "2.15.3_unofficial_t23_m2_WIP"
     const val liquibase = "4.2.2"
     const val liquibasePlugin = "2.0.4"
     const val logback = "1.2.3"
diff --git a/signalc/migrations/changelog.postgresql.sql b/signalc/migrations/changelog.postgresql.sql
index acb2dade9a645245a922b3cbaf224be2af6f4d96..cc095d00cfc96eb8bea824eb28aaeed88dd45c26 100644
--- a/signalc/migrations/changelog.postgresql.sql
+++ b/signalc/migrations/changelog.postgresql.sql
@@ -214,4 +214,9 @@ ALTER TABLE sessions ADD CONSTRAINT pk_sessions PRIMARY KEY (account_id, contact
 DROP INDEX identities_identity_key_bytes;
 -- rollback CREATE INDEX identities_identity_key_bytes ON identities (identity_key_bytes);
 
+
+-- changeset fdbk:1623280052786-1 failOnError:true
+ALTER TABLE contacts ALTER COLUMN phone_number DROP NOT NULL;
+-- rollback ALTER TABLE contacts ALTER COLUMN phone_number SET NOT NULL;
+
 -- TODO: uniqueness constraint on contacts account_id_uuid
\ No newline at end of file
diff --git a/signalc/src/main/kotlin/info/signalboost/signalc/db/Contacts.kt b/signalc/src/main/kotlin/info/signalboost/signalc/db/Contacts.kt
index 0853924f97273ecfaebd18ea280fac108357a849..f2cdce9e19c725d91961b0633c4aa1f01c5b6235 100644
--- a/signalc/src/main/kotlin/info/signalboost/signalc/db/Contacts.kt
+++ b/signalc/src/main/kotlin/info/signalboost/signalc/db/Contacts.kt
@@ -6,7 +6,7 @@ object Contacts: Table(), ContactRecord {
     override val accountId = varchar("account_id", 255)
     override val contactId = integer("contact_id").autoIncrement()
     val uuid = uuid("uuid").nullable()
-    val phoneNumber = varchar("phone_number", 255)
+    val phoneNumber = varchar("phone_number", 255).nullable()
     val profileKeyBytes = binary("profile_key_bytes").nullable()
 
     override val primaryKey = PrimaryKey(accountId, contactId)
diff --git a/signalc/src/main/kotlin/info/signalboost/signalc/logic/AccountManager.kt b/signalc/src/main/kotlin/info/signalboost/signalc/logic/AccountManager.kt
index 56f3ea8de3d3576fe5ef90aeb77589f85d15f640..c6e8a962b8c685ca6a08a0cd23d55a6b6fd53775 100644
--- a/signalc/src/main/kotlin/info/signalboost/signalc/logic/AccountManager.kt
+++ b/signalc/src/main/kotlin/info/signalboost/signalc/logic/AccountManager.kt
@@ -159,7 +159,7 @@ class AccountManager(private val app: Application) {
         val contactAccessKey = app.contactStore.loadProfileKey(accountId, contactId)?.let {
             UnidentifiedAccess.deriveAccessKeyFrom(it)
         } ?: run {
-            logger.error { "Could not derive delivery token for $contactId: no profile key found." }
+            logger.warn { "Could not derive delivery token for $contactId: no profile key found." }
             return null
         }
 
diff --git a/signalc/src/main/kotlin/info/signalboost/signalc/logic/SignalReceiver.kt b/signalc/src/main/kotlin/info/signalboost/signalc/logic/SignalReceiver.kt
index 3232b79565f7549a6f2960b9ab50ae9a060ab4cd..84804c7b468bff144521fd3f418c5d4d4b47d698 100644
--- a/signalc/src/main/kotlin/info/signalboost/signalc/logic/SignalReceiver.kt
+++ b/signalc/src/main/kotlin/info/signalboost/signalc/logic/SignalReceiver.kt
@@ -194,10 +194,23 @@ class SignalReceiver(private val app: Application) {
                 val dataMessage = contents.dataMessage.orNull()
                     ?: return@launch // drop other message types (eg: typing message, sync message, etc)
 
+                if (dataMessage.profileKey.isPresent) {
+                    logger.info { "Profile key: ${dataMessage.profileKey.get()}" }
+                } else {
+                    logger.info { "Profile key: NOT PRESENT" }
+                }
+
+                if (dataMessage.isProfileKeyUpdate) {
+                    logger.info { "Profile key update!!!" }
+                }
+
                 contactAddress = contents.sender.asSignalcAddress()
                 val body = dataMessage.body?.orNull() ?: ""
                 val attachments = dataMessage.attachments.orNull() ?: emptyList()
+
+                // TODO: check to see if we already have a profile key for sender, if no then write, if yes then only write if profile key update
                 dataMessage.profileKey.orNull()?.let {
+                    logger.info { "About to store profile key: ${dataMessage.profileKey}" }
                     app.contactStore.storeProfileKey(account.id, contactAddress.identifier, it)
                 }
 
@@ -225,7 +238,7 @@ class SignalReceiver(private val app: Application) {
             // NOTE: we should only receive N of these upon initiating a conversation with a new contact...
             // (where N is the number of devices a recipient has)
             // - q: should this also be able to store a phone number if we only have a UUID?
-            app.contactStore.storeUuid(
+            app.contactStore.storeUuidOrPhoneNumber(
                 accountId = account.username,
                 contactPhoneNumber =  envelope.sourceE164.get(),
                 contactUuid =  UUID.fromString(envelope.sourceUuid.get()),
@@ -234,21 +247,22 @@ class SignalReceiver(private val app: Application) {
         return null
     }
 
-
-    // TODO: make this function suspend and async/await!!
-    private fun processPreKeyBundle(envelope: SignalServiceEnvelope, account: VerifiedAccount) =
-        // here: app.coroutineScope.async(...)
-        app.coroutineScope.launch(Concurrency.Dispatcher) {
-            app.contactStore.create(
-                accountId = account.username,
-                phoneNumber = envelope.sourceE164.get(),
-                uuid = UUID.fromString(envelope.sourceUuid.get()),
-            )
+    private suspend fun processPreKeyBundle(envelope: SignalServiceEnvelope, account: VerifiedAccount) =
+        app.coroutineScope.async(Concurrency.Dispatcher) {
+            logger.info { "phoneNumber = ${envelope.sourceE164.get()}, uuid = ${envelope.sourceUuid.get()}" }
+            app.contactStore.resolveContactIdSuspend(account.username, envelope.sourceIdentifier) ?: run {
+                app.contactStore.create(
+                    accountId = account.username,
+                    phoneNumber = envelope.sourceE164.get(),
+                    uuid = UUID.fromString(envelope.sourceUuid.get()),
+                )
+                app.signalSender.sendProfileKey(account, envelope.asSignalcAddress())
+            }
             // If we are receiving a prekey bundle, this is the beginning of a new session, the initiation
             // of which might have depleted our prekey reserves below the level we want to keep on hand
             // to start new sessions. So: launch a background job to check our prekey reserve and replenish it if needed!
             app.accountManager.refreshPreKeysIfDepleted(account)
-        }
+        }.await()
 
     private suspend fun drop(envelope: SignalServiceEnvelope, account: VerifiedAccount): Job? {
         app.socketSender.send(
diff --git a/signalc/src/main/kotlin/info/signalboost/signalc/logic/SignalSender.kt b/signalc/src/main/kotlin/info/signalboost/signalc/logic/SignalSender.kt
index f42e66ca5ec04d8b70e3a7326297dac3ece74d81..af49f8ac3a07ab12b89627023ab5d66a6caef28c 100644
--- a/signalc/src/main/kotlin/info/signalboost/signalc/logic/SignalSender.kt
+++ b/signalc/src/main/kotlin/info/signalboost/signalc/logic/SignalSender.kt
@@ -118,6 +118,22 @@ class SignalSender(private val app: Application) {
                 .build()
         )
 
+    suspend fun sendProfileKey(
+        sender: VerifiedAccount,
+        recipient: SignalcAddress,
+        timestamp: Long = TimeUtil.nowInMillis(),
+    ): SignalcSendResult =
+        sendDataMessage(
+            sender,
+            recipient,
+            SignalServiceDataMessage
+                .newBuilder()
+                .asProfileKeyUpdate(true)
+                .withTimestamp(timestamp)
+                .withProfileKey(sender.profileKeyBytes)
+                .build()
+        )
+
     suspend fun setExpiration(
         sender: VerifiedAccount,
         recipient: SignalcAddress,
diff --git a/signalc/src/main/kotlin/info/signalboost/signalc/store/ContactStore.kt b/signalc/src/main/kotlin/info/signalboost/signalc/store/ContactStore.kt
index 3ee67148f7c7c209b378f013f7192f660bdba92c..8654976633f13cbb3121623c67ed69c987220b0f 100644
--- a/signalc/src/main/kotlin/info/signalboost/signalc/store/ContactStore.kt
+++ b/signalc/src/main/kotlin/info/signalboost/signalc/store/ContactStore.kt
@@ -50,38 +50,46 @@ class ContactStore(
             }
         }.resultedValues!!.single()[Contacts.contactId]
 
-    suspend fun resolveContactIdSuspend(accountId: String, identifier: String): Int =
+    private suspend fun resolveOrCreateContactIdSuspend(accountId: String, identifier: String): Int =
         newSuspendedTransaction (dispatcher, db) {
-            resolveContactId(accountId, identifier)
+            resolveOrCreateContactId(accountId, identifier)
         }
 
-    fun resolveContactIdBlocking(accountId: String, identifier: String): Int =
+    fun resolveOrCreateContactIdBlocking(accountId: String, identifier: String): Int =
         transaction(db) {
+            resolveOrCreateContactId(accountId, identifier)
+        }
+
+    private fun resolveOrCreateContactId(accountId: String, identifier: String): Int {
+        val uuid = parseUuid(identifier)
+        return resolveContactId(accountId, identifier) ?: runBlocking {
+            if(uuid == null) {
+                create(accountId, identifier, null)
+            } else {
+                create(accountId, null, uuid)
+            }
+        }
+    }
+
+    suspend fun resolveContactIdSuspend(accountId: String, identifier: String): Int? =
+        newSuspendedTransaction (dispatcher, db) {
             resolveContactId(accountId, identifier)
         }
 
-    private fun resolveContactId(accountId: String, identifier: String): Int {
+    // resolve contact id when one identifier available
+    private fun resolveContactId(accountId: String, identifier: String): Int? {
         val uuid = parseUuid(identifier)
         return run {
             uuid
                 ?.let {
-                    logger.debug { "Found uuid: $uuid" }
+                    // logger.debug { "Found uuid: $uuid" }
                     Contacts.select { (Contacts.accountId eq accountId).and(Contacts.uuid eq it) }
                 }
                 ?: Contacts.select {
-                    logger.debug { "Found phone number: $identifier" }
+                    // logger.debug { "Found phone number: $identifier" }
                     (Contacts.accountId eq accountId).and(Contacts.phoneNumber eq identifier)
                 }
-        }.single().let { it[Contacts.contactId] }
-//            }.singleOrNull()
-//            ?.let {
-//                it[Contacts.contactId]
-//            }
-//            ?: runBlocking {
-//                logger.debug { "Creating new contact w/ identifier: $uuid" }
-//                val phoneNumber: String? = if(uuid == null) identifier else null
-//                create(accountId, phoneNumber, uuid)
-//            }
+        }.singleOrNull()?.let { it[Contacts.contactId] }
     }
 
     private fun parseUuid(identifier: String): UUID? =
@@ -91,13 +99,17 @@ class ContactStore(
             null
         }
 
-    suspend fun storeUuid(accountId: String, contactPhoneNumber: String, contactUuid: UUID) {
-        // TODO: this currently only adds a uuid to a phonenumber-based contact record
-        // it shoudl be able to add either a uuid or aphonenumber
-        newSuspendedTransaction(dispatcher, db) {
+    suspend fun storeUuidOrPhoneNumber(accountId: String, contactPhoneNumber: String, contactUuid: UUID) {
+        newSuspendedTransaction(dispatcher, db){
             val contactId = resolveContactId(accountId, contactPhoneNumber)
+                ?: resolveContactId(accountId, contactUuid.toString())
+                ?: run {
+                    logger.warn { "No contact ID found for phone number: $contactPhoneNumber, uuid: $contactUuid" }
+                    return@newSuspendedTransaction
+                }
             Contacts.updateByContactId(accountId, contactId) {
                 it[Contacts.uuid] = contactUuid
+                it[Contacts.phoneNumber] = contactPhoneNumber
             }
         }
     }
@@ -107,7 +119,7 @@ class ContactStore(
             logger.warn { "Received profile key of invalid size ${profileKey.size} for account: $contactIdentifier" }
             return
         }
-        val contactId = resolveContactIdSuspend(accountId, contactIdentifier)
+        val contactId = resolveOrCreateContactIdSuspend(accountId, contactIdentifier)
         return newSuspendedTransaction(dispatcher, db) {
             Contacts.updateByContactId(accountId, contactId) {
                 it[Contacts.profileKeyBytes] = profileKey
@@ -117,9 +129,9 @@ class ContactStore(
 
     suspend fun loadProfileKey(accountId: String, contactIdentifier: String): ProfileKey? =
         newSuspendedTransaction(dispatcher, db) {
-            val contactId = resolveContactIdSuspend(accountId, contactIdentifier)
-            Contacts.findByContactId(accountId, contactId)?.let {
-                ProfileKey(it[Contacts.profileKeyBytes])
+            val contactId = resolveOrCreateContactIdSuspend(accountId, contactIdentifier)
+            Contacts.findByContactId(accountId, contactId)?.let { resultRow ->
+                resultRow[Contacts.profileKeyBytes]?.let{ ProfileKey(it) }
             }
         }
 
diff --git a/signalc/src/main/kotlin/info/signalboost/signalc/store/ProtocolStore.kt b/signalc/src/main/kotlin/info/signalboost/signalc/store/ProtocolStore.kt
index 516830e81267bce7376694e84b88f73b298ed38f..22473ecaf762a03417591572116f4e70f101d4ad 100644
--- a/signalc/src/main/kotlin/info/signalboost/signalc/store/ProtocolStore.kt
+++ b/signalc/src/main/kotlin/info/signalboost/signalc/store/ProtocolStore.kt
@@ -28,9 +28,7 @@ class ProtocolStore(val app: Application) {
     fun of(account: Account): AccountProtocolStore = AccountProtocolStore(
         db,
         account.username,
-        // TODO: if we need to resolve race in signal sender for first incoming message,
-        //  we might need to construct this in the constructor such that it consumes a session lock (for the account)...
-        app.contactStore::resolveContactIdBlocking
+        app.contactStore::resolveOrCreateContactIdBlocking
     )
 
     fun countOwnIdentities(): Long =