Guide
Buddy Verify
Optional install proof. Add a small snippet to your test build, and your testers confirm they installed and opened it with one tap instead of screenshots. It's never required.
How a verification works
- On a check-in, the tester taps Verify in app on the phone with your build.
- We create a one-time token, valid for 10 minutes, and open
bdb-<your verify app ID>://verify?t=…. - Your app signs the token with your secret (HMAC-SHA256) and sends it to us.
- We check the token and the signature, record the open for that day, and show the tester Verified ✅.
You see Installed and opened for each tester, on how many days they opened the app, and when they last did. Your request shows a Verified installs badge, and testers skip the screenshots on check-ins they verified. Reports and the final rating are still required.
What it proves, honestly
A verified open means someone opened a build containing your secret, on a phone, while signed in as that tester. Someone determined could pull the secret out of your app file and fake it, but that's far more work than just testing. Treat it as strong evidence, not as proof beyond doubt.
Privacy
The app sends three things: the one-time token, your verify app ID and the signature. No device ID, no analytics, nothing about the tester. Please keep it that way: don't log anything else around it.
2. Add the snippet
Android (Kotlin)
Add the link to the Activity that should open, then add BuddyVerify.kt and call it.
<!-- Inside the <activity> that should open, in AndroidManifest.xml -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bdb-YOUR_VERIFY_APP_ID" android:host="verify" />
</intent-filter>
// Buddy Verify for Android: tells Beta Dev Buddies that a tester opened this build.
// No dependencies. It sends only the one-time token, your verify app ID and a signature.
import android.app.Activity
import android.net.Uri
import java.net.HttpURLConnection
import java.net.URL
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import kotlin.concurrent.thread
object BuddyVerify {
private const val APP_ID = "YOUR_VERIFY_APP_ID"
private const val SECRET = "YOUR_VERIFY_SECRET"
private const val ENDPOINT = "https://betadevbuddies.com/api/verify"
/** Call with intent?.data from onCreate and onNewIntent. Returns true for a Buddy Verify link. */
fun handle(activity: Activity, uri: Uri?, onResult: (Boolean) -> Unit = {}): Boolean {
if (uri?.scheme != "bdb-$APP_ID" || uri.host != "verify") return false
val token = uri.getQueryParameter("t") ?: return false
thread {
val ok = runCatching {
val mac = Mac.getInstance("HmacSHA256").apply { init(SecretKeySpec(SECRET.toByteArray(), "HmacSHA256")) }
val signature = mac.doFinal(token.toByteArray()).joinToString("") { "%02x".format(it) }
val body = """{"verify_app_id":"$APP_ID","token":"$token","signature":"$signature"}"""
(URL(ENDPOINT).openConnection() as HttpURLConnection).run {
requestMethod = "POST"
doOutput = true
setRequestProperty("Content-Type", "application/json")
outputStream.use { it.write(body.toByteArray()) }
responseCode == 200
}
}.getOrDefault(false)
activity.runOnUiThread { onResult(ok) }
}
return true
}
}
// In that Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
BuddyVerify.handle(this, intent?.data) { ok ->
Toast.makeText(this, if (ok) "Verified ✅" else "Couldn't verify", Toast.LENGTH_SHORT).show()
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
BuddyVerify.handle(this, intent.data)
}
iOS (Swift, for the iOS beta)
Register the link scheme, add BuddyVerify.swift, and call it from .onOpenURL.
<!-- Info.plist: register the link scheme (or Target › Info › URL Types in Xcode) -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array><string>bdb-YOUR_VERIFY_APP_ID</string></array>
</dict>
</array>
// Buddy Verify for iOS: tells Beta Dev Buddies that a tester opened this build.
// No dependencies. It sends only the one-time token, your verify app ID and a signature.
import CryptoKit
import Foundation
enum BuddyVerify {
static let appID = "YOUR_VERIFY_APP_ID"
static let secret = "YOUR_VERIFY_SECRET"
static let endpoint = URL(string: "https://betadevbuddies.com/api/verify")!
/// Call from .onOpenURL (SwiftUI) or scene(_:openURLContexts:). Returns true for a Buddy Verify link.
@discardableResult
static func handle(_ url: URL, completion: @escaping (Bool) -> Void = { _ in }) -> Bool {
guard url.scheme == "bdb-\(appID)", url.host == "verify",
let token = URLComponents(url: url, resolvingAgainstBaseURL: false)?
.queryItems?.first(where: { $0.name == "t" })?.value else { return false }
let key = SymmetricKey(data: Data(secret.utf8))
let mac = HMAC<SHA256>.authenticationCode(for: Data(token.utf8), using: key)
let signature = mac.map { String(format: "%02x", $0) }.joined()
var request = URLRequest(url: endpoint)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(
withJSONObject: ["verify_app_id": appID, "token": token, "signature": signature])
URLSession.shared.dataTask(with: request) { _, response, _ in
let ok = (response as? HTTPURLResponse)?.statusCode == 200
DispatchQueue.main.async { completion(ok) }
}.resume()
return true
}
}
Other frameworks
Flutter and React Native snippets are coming. The snippets are open source, in the snippets/ folder of the Beta Dev Buddies repository, with a sample Android app.