Skip to main content
The Spam Control SDK blocks and identifies spam calls on iOS using Apple’s Call Directory Extensions. It manages the spam database, user-blocked numbers, and caller identification through the SpamControl singleton. Requires iOS 14+. The integration uses up to three Call Directory Extensions, each backed by its own App Group. Configure only the ones you need, since each feature requires its extension:

Integration

Step 1: Add the SDK to your project

The SDK is distributed as a precompiled FeatureSpamControl.xcframework, provided by the Bemobi team. Drag it into your Xcode project, then under each target’s General > Frameworks, Libraries, and Embedded Content:
  • Main app: add the framework as Embed & Sign.
  • Each extension (next steps): add it as Do Not Embed, so it is linked but not duplicated in the extension bundle.
Import it with import FeatureSpamControl.

Step 2: Create the Call Directory Extensions

Add one target per extension you need. For each, go to File > New > Target, select Call Directory Extension under iOS, and name it (e.g. SpamControlDatasetDirectory, SpamControlUserDirectory, SpamControlIdentificationDirectory).

Step 3: Configure App Groups

Create one App Group per extension and share it between the main app and the matching extension. In each target’s Signing & Capabilities tab, add the App Groups capability:
  • Main app: all three groups
  • Dataset extension: group.your.app.dataset
  • User extension: group.your.app.user
  • Identification extension: group.your.app.identification
Each extension must only have access to its own group. The same groups must be declared in each target’s .entitlements file. Example for the main app:
Each extension’s .entitlements lists only its own group.
The App Group identifiers are yours to choose, but they must be identical everywhere they appear: the capability, the .entitlements file, the handler’s appGroup: argument (Step 5), and the ExtensionBlockModel (Step 6).
For every extension target, add FeatureSpamControl under General > Frameworks, Libraries, and Embedded Content and set it to Do Not Embed (see Step 1).

Step 5: Implement the extension handlers

In each extension’s CallDirectoryHandler.swift, delegate to the matching Wave handler. The User and Identification handlers take a hintIdentifier: the label shown on the incoming call screen (e.g. "Blocked", "Spam"). Dataset extension (WaveCallDatasetDirectoryHandler):
User extension (WaveCallUserDirectoryHandler):
Identification extension (WaveCallIdentificationHandler):

Step 6: Add the contacts permission

Add NSContactsUsageDescription to your main app’s Info.plist. This is required only for manual blocking (User extension); the Dataset and Identification extensions work without contacts access.

Step 7: Configure the SDK

Configuration has two parts: declaring the extensions (once) and setting the API credentials (whenever they become available). Configure extensions. Call this once at startup (AppDelegate, @main, or an init service), passing one ExtensionBlockModel per extension. The appGroup and bundleIdentifier must match the App Groups from Step 3 and the extension targets’ bundle identifiers:
Set credentials and start protection. Call setup once you have the API key (provided by the Bemobi team) and the user’s phone number. It can be called after login; the phone number is not needed at launch. It must be called before any database operation, otherwise downloads fail with authentication errors.
setup can be called again if credentials change (e.g. a different user logs in); previous data is cleared automatically. Changing the environment between calls also clears all stored data.
Database operations require a physical device. downloadFullDatabase() throws on the iOS Simulator.

Example: enabling protection after login

Extensions are configured once at startup. Credentials and the database, however, are best set up right after the user logs in, when the phone number is available. A typical login handler looks like this:
The call order matters: setup first, then downloadFullDatabase, then the two apply... calls that load the data into the extensions.

Step 8: Enable Call Blocking & Identification

The user must enable the extensions in iOS Settings. Build and run on a physical device, then go to Settings > Phone > Call Blocking & Identification and toggle on each of your extensions. You can open this screen directly with SpamControl.shared.openSettings(). Changes may take a few moments to apply. If an extension does not appear or does not work, verify that the App Groups, entitlements, and bundle identifiers match across the main app and the extension.

API reference

All methods are accessed through SpamControl.shared and most are async/throws. Configuration helpers live on SpamControlConfiguration.shared. Only Brazilian numbers (55 + area code + number, e.g. 5511987654321) are processed.

Extension status

Report whether the user has enabled each extension under Settings > Phone > Call Blocking & Identification.
  • isCallDatasetDirectoryEnabled() -> Bool: whether the Dataset (spam database) extension is enabled.
  • isCallUserDirectoryEnabled() -> Bool: whether the User (manual blocking) extension is enabled.
  • isCallIdentificationDirectoryEnabled() -> Bool: whether the Identification (caller ID) extension is enabled.
  • areAllExtensionsEnabled() -> Bool: whether all three extensions are enabled.
  • openSettings(): opens the system Call Blocking & Identification screen (iOS 13.4+).

Spam database

  • downloadFullDatabase(): downloads the latest spam database. Throttled to once per 24h (a no-op if called sooner) and throws on the Simulator.
  • applySpamPhoneNumberBlocking(): loads the database into the Dataset extension so matching calls are blocked.
  • applyIdentificationSpamPhoneNumberBlocking(): loads the database into the Identification extension so matching calls are labeled.
  • totalPhoneNumbersAtDatabase() -> Int?: number of phone entries stored locally.
Call downloadFullDatabase() on each app launch to keep the database fresh; the 24h throttle prevents redundant downloads.

Categories

Categories let the user block whole groups of spam numbers at once.
  • downloadSpamCategories(cacheValidityDuration:) -> [SpamCategory]: lists available categories. Uses the cache while valid (default 3600s / 1h).
  • toggleCategoryBlockState(categoryId:) -> [SpamCategory]: flips one category’s block state and returns the updated list.
  • blockSpamPhoneNumberByCategories(for: [NSNumber]): persists the selected category IDs and reloads the extension.

Manual blocking

Require the contacts permission and an enabled User extension. Blocking and unblocking return the affected entry IDs ([Int64]) and throw a typed BlockManuallyErrorType (see Error handling).
  • blockUserPhoneNumber(phoneNumber:) -> [Int64]: blocks a single number.
  • blockUserPhoneNumbers(_: [String]) -> [Int64]: blocks several numbers at once.
  • retrievedBlockedUserPhoneNumbers() -> [CXCallDirectoryPhoneNumber]: lists the numbers the user has blocked.
  • unblockPhoneNumber(phoneNumber:) -> [Int64]: removes a number from the block list.

Trusted numbers

Trusted numbers are an allowlist: a trusted number is never blocked or labeled as spam, even if it appears in the spam database. Adding or removing one reloads the affected extensions. The throwing methods throw TrustedNumbersErrorType.
  • addTrustedNumber(_ phoneNumber: String): marks a number as trusted so it is no longer blocked.
  • removeTrustedNumber(_ phoneNumber: String): removes a number from the trusted list, restoring its previous blocking state.
  • getTrustedNumbers() -> [String]: lists the user’s trusted numbers.

Statistics

  • totalBlockedCalls() -> TotalBlockedCalls?: total number of calls blocked, fetched from the server.

Network

The SDK monitors connectivity to control downloads. Cellular downloads are disabled by default.
  • isNetworkConnected: Bool: whether the device has any connection.
  • isOnWiFi: Bool: whether the device is on Wi-Fi.
  • downloadWithCellularDataEnabled: Bool: set to true to allow downloads over cellular (default false).

Configuration helpers

On SpamControlConfiguration.shared:
  • getBundleIdentifierExtension(with:) -> String: the bundle identifier configured for an extension type.
  • getAppGroupIdentifier(for:) -> String: the App Group configured for an extension type.
  • getSDKEnviroment() -> SDKEnviroment: the current environment (.production or .stage).

Disabling the SDK

  • disabledSDK(): turns the SDK off. It removes the CallKit data from every extension, deletes the local spam database, and clears stored state (categories, last download time, etc.). To turn protection back on, call setup and downloadFullDatabase again.

Error handling

The manual blocking methods (blockUserPhoneNumber, blockUserPhoneNumbers, unblockPhoneNumber) throw a typed BlockManuallyErrorType you can switch on to give the user a precise message:
Other operations (database download, categories, statistics) surface failures as a generic Error. Read error.localizedDescription for the cause, such as an authentication failure or an unavailable network:

SDK size

  • 3 to 7 MB: core functionality (blocking and call management), no UI.
  • 10 to 15 MB: full functionality including a SwiftUI user interface.

Migration to 2.0

The Wave class is deprecated. Use SpamControlConfiguration instead.