From dbbc43e311371f81a4791215aabf85fed60ae31a Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 3 Jul 2026 12:50:10 +0200 Subject: [PATCH 1/4] feat: resolve preview paywall by id instead of fetching all paywalls The debug/preview flow fetched ALL paywalls for the app just to translate the deep-link numeric `paywall_id` into the paywall `identifier`, which is slow and breaks for apps with many paywalls. Add a single-lookup resolver: `GET /v2/paywalls/resolve?id=` on the V2 API returns `{ id, identifier, name }` for a paywall id, scoped to the app's public key. The preview flow now resolves the identifier with one request, then fetches that one paywall exactly as before. - Add `.paywallsV2` endpoint host (base host, `/v2/` prefix) - Add `Endpoint.resolvePaywall(byDatabaseId:)` + `Network.resolvePaywallIdentifier` (authenticated with the public key via `isForDebugging: false`) - Rewrite `DebugViewController` preview to use the resolver; drop the fetch-all - Remove the now-unused `Paywalls` list model and `getPaywalls()` - The "Your Paywalls" multi-paywall picker is removed (it depended on the fetch-all); previewing one paywall by id is unaffected Requires the backend resolver endpoint (superwall/paywall-next#3456). Co-Authored-By: Claude Opus 4.8 --- .../Debug/DebugViewController.swift | 41 +++++++++++-------- .../SuperwallKit/Models/Paywall/Paywall.swift | 21 +++++++++- Sources/SuperwallKit/Network/API.swift | 17 ++++++++ Sources/SuperwallKit/Network/Endpoint.swift | 21 +++++++--- Sources/SuperwallKit/Network/Network.swift | 22 +++++++--- .../Network/NetworkTests.swift | 23 +++++++++++ 6 files changed, 115 insertions(+), 30 deletions(-) diff --git a/Sources/SuperwallKit/Debug/DebugViewController.swift b/Sources/SuperwallKit/Debug/DebugViewController.swift index 37a7e31a62..e6b77fdd39 100644 --- a/Sources/SuperwallKit/Debug/DebugViewController.swift +++ b/Sources/SuperwallKit/Debug/DebugViewController.swift @@ -118,6 +118,9 @@ final class DebugViewController: UIViewController { var paywallDatabaseId: String? var paywallIdentifier: String? var paywall: Paywall? + /// Backed the "Your Paywalls" picker. Retained (always empty) now that the + /// preview flow resolves a single paywall by id instead of fetching all of + /// them; see `pressedPreview`. var paywalls: [Paywall] = [] var previewViewContent: UIView? private var cancellable: AnyCancellable? @@ -204,32 +207,31 @@ final class DebugViewController: UIViewController { func loadPreview() async { activityIndicator.startAnimating() previewViewContent?.removeFromSuperview() + await finishLoadingPreview() + } - if paywalls.isEmpty { + func finishLoadingPreview() async { + var paywallId: String? + + if let paywallIdentifier = paywallIdentifier { + paywallId = paywallIdentifier + } else if let paywallDatabaseId = paywallDatabaseId { + // Resolve the numeric database id from the deep link to the paywall's + // identifier (slug) with a single lookup, rather than fetching every + // paywall for the app and filtering in memory. do { - paywalls = try await network.getPaywalls() - await finishLoadingPreview() + let resolution = try await network.resolvePaywallIdentifier(forDatabaseId: paywallDatabaseId) + paywallId = resolution.identifier + paywallIdentifier = resolution.identifier } catch { Logger.debug( logLevel: .error, scope: .debugViewController, - message: "Failed to Fetch Paywalls", + message: "Failed to Resolve Paywall", error: error ) + return } - } else { - await finishLoadingPreview() - } - } - - func finishLoadingPreview() async { - var paywallId: String? - - if let paywallIdentifier = paywallIdentifier { - paywallId = paywallIdentifier - } else if let paywallDatabaseId = paywallDatabaseId { - paywallId = paywalls.first { $0.databaseId == paywallDatabaseId }?.identifier - paywallIdentifier = paywallId } else { return } @@ -313,6 +315,11 @@ final class DebugViewController: UIViewController { @objc func pressedPreview() { guard let id = paywallDatabaseId else { return } + // The "Your Paywalls" picker listed every paywall from the (now removed) + // fetch-all. Preview opens one specific paywall by id, so there is no list + // to choose from; bail out rather than present an empty sheet. + guard !paywalls.isEmpty else { return } + let options: [AlertOption] = paywalls.map { paywall in var name = paywall.name diff --git a/Sources/SuperwallKit/Models/Paywall/Paywall.swift b/Sources/SuperwallKit/Models/Paywall/Paywall.swift index 3a41557e58..97cbf3019c 100644 --- a/Sources/SuperwallKit/Models/Paywall/Paywall.swift +++ b/Sources/SuperwallKit/Models/Paywall/Paywall.swift @@ -8,8 +8,25 @@ import UIKit -struct Paywalls: Decodable { - var paywalls: [Paywall] +/// The minimal paywall metadata returned by the V2 resolver endpoint +/// (`GET /v2/paywalls/resolve`). +/// +/// The debug/preview deep link carries a numeric paywall database `id`, but the +/// full-paywall fetch is keyed by `identifier` (slug). This lets the preview +/// flow translate `id` → `identifier` with a single lookup instead of fetching +/// every paywall for the app. +/// +/// Decoded with `JSONDecoder.fromSnakeCase`; the endpoint also returns +/// `application_id`, which is not needed here and is ignored. +struct PaywallIdentifierResolution: Decodable { + /// The id of the paywall in the database. + let id: String + + /// The identifier (slug) of the paywall, used to fetch the full paywall. + let identifier: String + + /// The display name of the paywall. + let name: String } struct Paywall: Codable { diff --git a/Sources/SuperwallKit/Network/API.swift b/Sources/SuperwallKit/Network/API.swift index 495f2572a0..5c391fd0b5 100644 --- a/Sources/SuperwallKit/Network/API.swift +++ b/Sources/SuperwallKit/Network/API.swift @@ -13,6 +13,7 @@ enum EndpointHost { case enrichment case adServices case subscriptionsApi + case paywallsV2 } protocol ApiHostConfig { @@ -34,6 +35,7 @@ struct Api { let enrichment: Enrichment let adServices: AdServices let subscriptionsApi: SubscriptionsAPI + let paywallsV2: PaywallsV2 init(networkEnvironment: SuperwallOptions.NetworkEnvironment) { base = Base(networkEnvironment: networkEnvironment) @@ -41,6 +43,7 @@ struct Api { enrichment = Enrichment(networkEnvironment: networkEnvironment) adServices = AdServices(networkEnvironment: networkEnvironment) subscriptionsApi = SubscriptionsAPI(networkEnvironment: networkEnvironment) + paywallsV2 = PaywallsV2(networkEnvironment: networkEnvironment) } func getConfig(host: EndpointHost) -> ApiHostConfig { @@ -55,6 +58,8 @@ struct Api { return adServices case .subscriptionsApi: return subscriptionsApi + case .paywallsV2: + return paywallsV2 } } @@ -109,4 +114,16 @@ struct Api { self.networkEnvironment = networkEnvironment } } + + /// The V2 API served under the `/v2/` prefix on the base host + /// (e.g. `superwall.com/v2/*`, which is routed to the V2 Worker). + struct PaywallsV2: ApiHostConfig { + let networkEnvironment: SuperwallOptions.NetworkEnvironment + var host: String { return networkEnvironment.baseHost } + var path: String { return "/v2/" } + + init(networkEnvironment: SuperwallOptions.NetworkEnvironment) { + self.networkEnvironment = networkEnvironment + } + } } diff --git a/Sources/SuperwallKit/Network/Endpoint.swift b/Sources/SuperwallKit/Network/Endpoint.swift index 7a4b9b9a49..8cea6c0a4a 100644 --- a/Sources/SuperwallKit/Network/Endpoint.swift +++ b/Sources/SuperwallKit/Network/Endpoint.swift @@ -207,15 +207,26 @@ extension Endpoint where } } -// MARK: - PaywallsResponse +// MARK: - PaywallIdentifierResolution extension Endpoint where Kind == EndpointKinds.Superwall, - Response == Paywalls { - static func paywalls() -> Self { + Response == PaywallIdentifierResolution { + /// Resolves a numeric paywall database id to its identifier (slug) via the V2 + /// resolver endpoint (`GET /v2/paywalls/resolve?id=`). + /// + /// Used by the debug/preview flow so it no longer has to fetch every paywall + /// for the app just to translate a deep-link `paywall_id` into an identifier. + /// Authenticated with the app's public key (see `Network.resolvePaywallIdentifier`). + static func resolvePaywall( + byDatabaseId databaseId: String, + retryCount: Int + ) -> Self { return Endpoint( + retryCount: retryCount, components: Components( - host: .base, - path: "paywalls" + host: .paywallsV2, + path: "paywalls/resolve", + queryItems: [URLQueryItem(name: "id", value: databaseId)] ), method: .get ) diff --git a/Sources/SuperwallKit/Network/Network.swift b/Sources/SuperwallKit/Network/Network.swift index bd863ef451..4c22b39520 100644 --- a/Sources/SuperwallKit/Network/Network.swift +++ b/Sources/SuperwallKit/Network/Network.swift @@ -122,21 +122,31 @@ class Network { } } - func getPaywalls() async throws -> [Paywall] { + /// Resolves a numeric paywall database id to its identifier (slug) so the + /// debug/preview flow can fetch a single paywall instead of all of them. + /// + /// Authenticated with the app's public key (`isForDebugging: false`), which + /// `makeHeaders` sends as `Authorization: Bearer `. + func resolvePaywallIdentifier( + forDatabaseId databaseId: String, + retryCount: Int = 6 + ) async throws -> PaywallIdentifierResolution { do { - let response = try await urlSession.request( - .paywalls(), + return try await urlSession.request( + .resolvePaywall( + byDatabaseId: databaseId, + retryCount: retryCount + ), data: SuperwallRequestData( factory: factory, - isForDebugging: true + isForDebugging: false ) ) - return response.paywalls } catch { Logger.debug( logLevel: .error, scope: .network, - message: "Request Failed: /paywalls", + message: "Request Failed: /v2/paywalls/resolve", error: error ) throw error diff --git a/Tests/SuperwallKitTests/Network/NetworkTests.swift b/Tests/SuperwallKitTests/Network/NetworkTests.swift index ac9f1fd86f..086163654d 100644 --- a/Tests/SuperwallKitTests/Network/NetworkTests.swift +++ b/Tests/SuperwallKitTests/Network/NetworkTests.swift @@ -179,4 +179,27 @@ struct NetworkTests { #expect(bodyJson["deviceId"] as? String == "device_123") #expect(bodyJson["appUserId"] as? String == "user_123") } + + @Test func resolvePaywall_endpointBuildsRequest() async throws { + let dependencyContainer = DependencyContainer() + let endpoint = Endpoint.resolvePaywall( + byDatabaseId: "123", + retryCount: 6 + ) + + let urlRequest = await endpoint.makeRequest( + with: SuperwallRequestData(factory: dependencyContainer, isForDebugging: false), + factory: dependencyContainer + ) + + #expect(urlRequest?.httpMethod == "GET") + + let urlString = try #require(urlRequest?.url?.absoluteString) + #expect(urlString.contains("/v2/paywalls/resolve")) + #expect(urlString.contains("id=123")) + + // The resolver authenticates with the public key (isForDebugging: false), + // so an Authorization header is attached. + #expect(urlRequest?.value(forHTTPHeaderField: "Authorization") != nil) + } } From c6cde4b1d665a7bc41da405060a379085c871ce9 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 3 Jul 2026 17:18:08 +0200 Subject: [PATCH 2/4] refactor(debug): remove dead preview-picker path With the fetch-all gone, the "Your Paywalls" multi-paywall picker can no longer be populated, so `pressedPreview()` was unreachable and the picker chip still advertised a dropdown that did nothing. - Remove `pressedPreview()` and the always-empty `paywalls` property - Drop the picker tap target from the name chip and the preview container - Remove the down-arrow affordance; the chip is now a display-only name label (renamed `previewPickerButton` -> `previewNameButton`) Co-Authored-By: Claude Opus 4.8 --- .../Debug/DebugViewController.swift | 63 +++---------------- 1 file changed, 10 insertions(+), 53 deletions(-) diff --git a/Sources/SuperwallKit/Debug/DebugViewController.swift b/Sources/SuperwallKit/Debug/DebugViewController.swift index e6b77fdd39..6391fa404b 100644 --- a/Sources/SuperwallKit/Debug/DebugViewController.swift +++ b/Sources/SuperwallKit/Debug/DebugViewController.swift @@ -75,7 +75,7 @@ final class DebugViewController: UIViewController { return button }() - lazy var previewPickerButton: SWBounceButton = { + lazy var previewNameButton: SWBounceButton = { let button = SWBounceButton() button.setTitle("", for: .normal) button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14) @@ -86,14 +86,11 @@ final class DebugViewController: UIViewController { button.setTitleColor(primaryColor, for: .normal) button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 10, bottom: 6, right: 10) button.translatesAutoresizingMaskIntoConstraints = false - button.imageView?.tintColor = primaryColor button.layer.cornerRadius = 10 - - let image = UIImage(named: "SuperwallKit_down_arrow", in: Bundle.module, compatibleWith: nil)! - button.semanticContentAttribute = .forceRightToLeft - button.setImage(image, for: .normal) - button.imageView?.tintColor = primaryColor - button.addTarget(self, action: #selector(pressedPreview), for: .primaryActionTriggered) + // Display-only chip showing the previewed paywall's name. The multi-paywall + // picker (a dropdown opened from this chip) was removed when the preview + // flow stopped fetching all paywalls, so it no longer responds to taps. + button.isUserInteractionEnabled = false return button }() @@ -111,17 +108,12 @@ final class DebugViewController: UIViewController { let button = SWBounceButton() button.shouldAnimateLightly = true button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget(self, action: #selector(pressedPreview), for: .primaryActionTriggered) return button }() var paywallDatabaseId: String? var paywallIdentifier: String? var paywall: Paywall? - /// Backed the "Your Paywalls" picker. Retained (always empty) now that the - /// preview flow resolves a single paywall by id instead of fetching all of - /// them; see `pressedPreview`. - var paywalls: [Paywall] = [] var previewViewContent: UIView? private var cancellable: AnyCancellable? private var initialLocaleIdentifier: String? @@ -168,7 +160,7 @@ final class DebugViewController: UIViewController { view.addSubview(consoleButton) view.addSubview(exitButton) view.addSubview(bottomButton) - previewContainerView.addSubview(previewPickerButton) + previewContainerView.addSubview(previewNameButton) view.backgroundColor = lightBackgroundColor previewContainerView.clipsToBounds = false @@ -198,9 +190,9 @@ final class DebugViewController: UIViewController { bottomButton.widthAnchor.constraint(equalTo: view.layoutMarginsGuide.widthAnchor, constant: -40), bottomButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: -10), - previewPickerButton.centerXAnchor.constraint(equalTo: previewContainerView.centerXAnchor, constant: 0), - previewPickerButton.heightAnchor.constraint(equalToConstant: 26), - previewPickerButton.centerYAnchor.constraint(equalTo: previewContainerView.bottomAnchor) + previewNameButton.centerXAnchor.constraint(equalTo: previewContainerView.centerXAnchor, constant: 0), + previewNameButton.heightAnchor.constraint(equalToConstant: 26), + previewNameButton.centerYAnchor.constraint(equalTo: previewContainerView.bottomAnchor) ]) } @@ -250,7 +242,7 @@ final class DebugViewController: UIViewController { paywall.productVariables = productVariables self.paywall = paywall - self.previewPickerButton.setTitle("\(paywall.name)", for: .normal) + self.previewNameButton.setTitle("\(paywall.name)", for: .normal) self.activityIndicator.stopAnimating() self.addPaywallPreview() } catch { @@ -312,41 +304,6 @@ final class DebugViewController: UIViewController { } } - @objc func pressedPreview() { - guard let id = paywallDatabaseId else { return } - - // The "Your Paywalls" picker listed every paywall from the (now removed) - // fetch-all. Preview opens one specific paywall by id, so there is no list - // to choose from; bail out rather than present an empty sheet. - guard !paywalls.isEmpty else { return } - - let options: [AlertOption] = paywalls.map { paywall in - var name = paywall.name - - if id == paywall.databaseId { - name = "\(name) ✓" - } - - let alert = AlertOption( - title: name, - action: { [weak self] in - self?.paywallDatabaseId = paywall.databaseId - self?.paywallIdentifier = paywall.identifier - Task { await self?.loadPreview() } - }, - style: .default - ) - return alert - } - - presentAlert( - title: nil, - message: "Your Paywalls", - options: options, - on: previewPickerButton - ) - } - @objc func pressedExitButton() { Task { await debugManager.closeDebugger(animated: false) From b073ddabe39d42a21c9d090c612984bc84a55769 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 3 Jul 2026 17:23:04 +0200 Subject: [PATCH 3/4] fix(network): point V2 resolver at api.superwall.com, not baseHost The V2 API is served from the `superwall.com` domain (api.superwall.com in production, api.superwall.dev in developer), which is a DIFFERENT apex domain than `baseHost` (the legacy v1 API on api.superwall.me). Using baseHost would have sent the resolver to api.superwall.me/v2/... in production and 404'd. Add a dedicated `NetworkEnvironment.apiV2Host` (mirroring `enrichmentHost`, another superwall.com-domain service) and point the `PaywallsV2` host config at it. Local uses localhost:3001 (the apps/api wrangler dev port). Co-Authored-By: Claude Opus 4.8 --- .../Config/Options/SuperwallOptions.swift | 18 ++++++++++++++++++ Sources/SuperwallKit/Network/API.swift | 7 ++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift b/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift index 6c1bf36d07..c39e7ab207 100644 --- a/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift +++ b/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift @@ -216,6 +216,24 @@ public final class SuperwallOptions: NSObject, Encodable { } } + /// Host for the Superwall V2 API (the `apps/api` Cloudflare Worker), whose + /// routes live under a `/v2/` path. + /// + /// This is a DIFFERENT host from ``baseHost`` (the legacy v1 API on + /// `api.superwall.me`): the V2 API is served from the `superwall.com` + /// domain — `api.superwall.com` in production and `api.superwall.dev` in the + /// developer/staging environment. + var apiV2Host: String { + switch self { + case .developer: + return "api.superwall.dev" + case .local: + return "localhost:3001" + default: + return "api.superwall.com" + } + } + /// The base URL for the Superwall dashboard. var dashboardBaseUrl: String { switch self { diff --git a/Sources/SuperwallKit/Network/API.swift b/Sources/SuperwallKit/Network/API.swift index df0797e730..d8a45fd318 100644 --- a/Sources/SuperwallKit/Network/API.swift +++ b/Sources/SuperwallKit/Network/API.swift @@ -120,11 +120,12 @@ struct Api { } } - /// The V2 API served under the `/v2/` prefix on the base host - /// (e.g. `superwall.com/v2/*`, which is routed to the V2 Worker). + /// The Superwall V2 API, served under a `/v2/` path on `api.superwall.com` + /// (production) / `api.superwall.dev` (developer). See + /// `NetworkEnvironment.apiV2Host`. struct PaywallsV2: ApiHostConfig { let networkEnvironment: SuperwallOptions.NetworkEnvironment - var host: String { return networkEnvironment.baseHost } + var host: String { return networkEnvironment.apiV2Host } var path: String { return "/v2/" } init(networkEnvironment: SuperwallOptions.NetworkEnvironment) { From fb00fc48d7eaffd6d345fa8fa3d4d987b8149bed Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 05:44:00 +0000 Subject: [PATCH 4/4] fix: resolve paywall preview via debugger signed token instead of app key --- CHANGELOG.md | 6 ++++++ Sources/SuperwallKit/Network/Endpoint.swift | 4 +++- Sources/SuperwallKit/Network/Network.swift | 8 +++++--- Tests/SuperwallKitTests/Network/NetworkTests.swift | 10 ++++++---- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d06cfe70fa..41643aad40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall/Superwall-iOS/releases) on GitHub. +## Unreleased + +### Fixes + +- Fixes the debugger paywall preview so its resolve request authenticates with the debugger's signed preview token instead of the app's public API key. + ## 4.16.2 ### Enhancements diff --git a/Sources/SuperwallKit/Network/Endpoint.swift b/Sources/SuperwallKit/Network/Endpoint.swift index e94dc45ff3..994f53664a 100644 --- a/Sources/SuperwallKit/Network/Endpoint.swift +++ b/Sources/SuperwallKit/Network/Endpoint.swift @@ -216,7 +216,9 @@ extension Endpoint where /// /// Used by the debug/preview flow so it no longer has to fetch every paywall /// for the app just to translate a deep-link `paywall_id` into an identifier. - /// Authenticated with the app's public key (see `Network.resolvePaywallIdentifier`). + /// Authenticated with the debugger's signed preview token (the `sat_` token + /// from the deeplink, sent as `Authorization: Bearer `), not the + /// app's public key (see `Network.resolvePaywallIdentifier`). static func resolvePaywall( byDatabaseId databaseId: String, retryCount: Int diff --git a/Sources/SuperwallKit/Network/Network.swift b/Sources/SuperwallKit/Network/Network.swift index 9ea852a18a..0846fe6ba5 100644 --- a/Sources/SuperwallKit/Network/Network.swift +++ b/Sources/SuperwallKit/Network/Network.swift @@ -125,8 +125,10 @@ class Network { /// Resolves a numeric paywall database id to its identifier (slug) so the /// debug/preview flow can fetch a single paywall instead of all of them. /// - /// Authenticated with the app's public key (`isForDebugging: false`), which - /// `makeHeaders` sends as `Authorization: Bearer `. + /// Authenticated with the debugger's signed preview token (the `sat_` token + /// from the debugger deeplink, stored in `storage.debugKey`) via + /// `isForDebugging: true`, which `makeHeaders` sends as + /// `Authorization: Bearer ` — not the app's public key. func resolvePaywallIdentifier( forDatabaseId databaseId: String, retryCount: Int = 6 @@ -139,7 +141,7 @@ class Network { ), data: SuperwallRequestData( factory: factory, - isForDebugging: false + isForDebugging: true ) ) } catch { diff --git a/Tests/SuperwallKitTests/Network/NetworkTests.swift b/Tests/SuperwallKitTests/Network/NetworkTests.swift index 086163654d..0eee86c92f 100644 --- a/Tests/SuperwallKitTests/Network/NetworkTests.swift +++ b/Tests/SuperwallKitTests/Network/NetworkTests.swift @@ -182,13 +182,14 @@ struct NetworkTests { @Test func resolvePaywall_endpointBuildsRequest() async throws { let dependencyContainer = DependencyContainer() + dependencyContainer.storage.debugKey = "sat_test" let endpoint = Endpoint.resolvePaywall( byDatabaseId: "123", retryCount: 6 ) let urlRequest = await endpoint.makeRequest( - with: SuperwallRequestData(factory: dependencyContainer, isForDebugging: false), + with: SuperwallRequestData(factory: dependencyContainer, isForDebugging: true), factory: dependencyContainer ) @@ -198,8 +199,9 @@ struct NetworkTests { #expect(urlString.contains("/v2/paywalls/resolve")) #expect(urlString.contains("id=123")) - // The resolver authenticates with the public key (isForDebugging: false), - // so an Authorization header is attached. - #expect(urlRequest?.value(forHTTPHeaderField: "Authorization") != nil) + // The resolver authenticates with the debugger's signed preview token + // (isForDebugging: true), so the Authorization header carries the + // `sat_` debug key as a bearer token, not the app's public key. + #expect(urlRequest?.value(forHTTPHeaderField: "Authorization") == "Bearer sat_test") } }