Ads¶
Prove to your advertising partners that an impression or a click came from a real person. For each moment of an ad (on screen, clicked, gone) the SDK signs the current score into a proof with the device key, and sends it to DEA's recording service, which keeps it so it can be verified later.
The SDK does not request, render or wrap ads. Your ad stack keeps doing that.
Note: iOS has the proof calls, not the Web ad module. You send the three proofs yourself with
sendProof, and the SDK does not check that the user touched the ad.
What iOS has¶
The Web SDK has an ad module: ads.session_opened(surface?,
adUnit?) sends the impression and resolves an AdSession whose id, click() and end() carry
the rest, ads.counts counts what happened, and a click is refused when nothing touched the ad's
element. iOS has neither the module nor the touch check. It has the calls the module is built on:
sendProof, record and getProof.
| Web | iOS | |
|---|---|---|
| The ad is on screen | ads.session_opened(surface?, adUnit?) |
sendProof(event: .adImpression, sessionId: ""), and keep the sessionId DEA answers with |
| The ad is clicked | ad.click() |
sendProof(event: .adClick, sessionId: id) |
| The ad is gone | ad.end() |
sendProof(event: .adEnded, sessionId: id, update: true) |
| The click is checked against touches | Yes, when you pass the ad's element | No: your app declares it |
| One click per ad, nothing after the end | Enforced by the SDK | Up to you |
| What happened | ads.counts |
The RecordOutcome each call returns |
When to use it¶
- You sell ad inventory and want to show buyers verified human traffic
- You want each impression, click and end of an ad recorded with the score at that moment
Prerequisites¶
- The SDK is installed and started with your
appIdandappKey. See the Quick Start. WithoutappKey, nothing is sent. registryAppIdset to yourappId. Proofs carry it as theirappId, and DEA accepts a proof only when it names the app behindappKey. Leftnil, proofs carry a built-in development id. See Accounts and keys.- The device key registered: call
attest()and get.passedbefore the impression. The SDK does not register the key by itself and does not check that you did: without a registration, proofs are still signed and sent, and DEA refuses them, as it refuses every proof signed by a key it has not registered. - The user's consent, when
requireConsentis on (the default). Before it, nothing is recorded, so the session never becomes sufficient and nothing is signed.
The Quick Start's holder, with registryAppId added:
import DeaSDK
enum Dea {
/// The app's one DEA SDK instance. Nil until `start()` has run.
private(set) static var sdk: DeaSdk?
static func start() {
guard sdk == nil else { return }
let dea = DeaSdk(
options: DeaSdkOptions(
appId: "YOUR_APP_ID",
appKey: "YOUR_APP_KEY",
registryAppId: "YOUR_APP_ID"
)
)
dea.start()
sdk = dea
}
}
Usage¶
sendProof makes network requests and blocks the calling thread until they finish. Never call
it on the main thread.
1. Send the impression when the ad is on screen¶
import DeaSDK
import Foundation
DispatchQueue.global(qos: .utility).async {
guard let dea = Dea.sdk, dea.attest().verdict == .passed else { return }
let outcome = dea.sendProof(event: .adImpression, sessionId: "", adUnit: "YOUR_AD_UNIT")
guard outcome.accepted, let adSessionId = outcome.sessionId else {
print("DEA impression not recorded: \(outcome.said ?? "")")
return
}
print("DEA ad session \(adSessionId)") // keep it for the click and the end
}
- Pass
sessionId: "". The impression is sent with an empty session id, and DEA answers with the id of the ad session it opened;outcome.sessionIdcarries it. LeavesessionIdout and the SDK signs the id of the SDK's own session (sessionInfo.session) instead, which is not an ad session. adUnitis signed into the proof asad_unit. Pass the same one to the impression, the click and the end.outcome.sessionIdisnilwhen DEA did not accept the impression or its answer named no id. Then there is no ad session to send a click or an end against.
2. Send the click and the end with DEA's id¶
import DeaSDK
import Foundation
DispatchQueue.global(qos: .utility).async {
guard let dea = Dea.sdk else { return }
// The user clicked the ad.
_ = dea.sendProof(event: .adClick, sessionId: adSessionId, adUnit: "YOUR_AD_UNIT")
// Later: the ad is gone.
_ = dea.sendProof(event: .adEnded, sessionId: adSessionId, adUnit: "YOUR_AD_UNIT", update: true)
}
adSessionId is the id from step 1. The impression and the click are sent as
POST /api/sessions; update: true sends the end as PUT /api/sessions/{adSessionId}, the update
of the ad session the impression opened. Send them in this order, each after the one before has
returned: the Example below uses one serial queue for that.
Note: The click is not checked against touches on iOS. The SDK signs and sends a click whenever your app calls
sendProof(event: .adClick, …), without looking at what the user touched, and it does not stop a second click or a click after the end. Your app declares the click: send one per ad, when your ad SDK reports it.
What each call does¶
sendProofsigns one proof and sends it. It returns aRecordOutcome: whether DEA accepted the proof, the proof itself, what DEA said, and the ad session id DEA answered with.recordis the same call, and returns only whether DEA accepted the proof.getProofsigns one proof and returns it as aSignedProofwithout sending it, for an app that delivers proofs its own way.
Each call asks DEA for a single-use nonce before it signs, so all three block, getProof included.
When DEA gives none (no appKey, no answer, or a refusal), the proof is signed without one. The
proof carries the current score, written by the
scoring engine and signed with the device key.
sendProof and record then send the proof. When DEA does not answer or answers with a 5xx, the
SDK sends it again after 2, 4, 8 and 16 seconds (plus up to one second each), five sends at most,
and never after the proof's 90-second lifetime. Each request can take up to 15 seconds, so a call
can block for up to about two minutes when requests time out. A 4xx answer is final.
The attestation parameter decides only the tier signed
into the proof. Left nil, the SDK uses the registration it holds: B when it passed with an App
Attest token (platformAttested) and has not expired, C otherwise. extra adds fields of your
own to the request body, beside the proof and outside its signature; DEA trusts nothing in it, and
it never replaces the proof itself. Its values must be JSON values.
Read why a proof was not recorded¶
sendProof never throws. When accepted is false, said says why:
| Why | isSigned |
said |
|---|---|---|
| The session is not sufficient yet | false |
nothing signed: core refused -33: the session is not scored enough yet |
| No device key could be created on this device | false |
no device key on this handset |
The scoring engine is not running (ready is false) |
false |
nothing signed: no core loaded, so nothing is composed |
No appKey: nothing is sent |
true |
no appKey: a production account must be given the key it was issued, DeaSdkOptions.appKey |
DEA refused the proof (a 4xx answer) |
true |
The path, DEA's status and its words, such as /api/sessions answered 401: … |
DEA did not answer, or answered 5xx, five times |
true |
The last failure, such as unreachable: … |
| The proof expired before a send got through | true |
proof expired before it was delivered |
getProof returns nil in the first three cases. A session that has just started is not sufficient
until it has as many actions as the
model asks for, 8 with the built-in model (see
Evidence). Until then nothing is signed, so an
impression sent then is not recorded, and the SDK does not keep it to send later. Check that
getClassification().result is not .insufficient before the impression, as the Example does, rather than spend a
request on a refusal. When the impression is refused, there is no ad session for the click and the
end.
Read the classification before you request an ad¶
You can also read the classification before you request an ad, and decide what to do with it in your own ad stack — see Human Scoring.
Examples¶
One ad, from on screen to gone¶
import DeaSDK
import Foundation
/// The proofs for one ad, from on screen to gone. Each method returns at once; the proofs are
/// sent in order on a background queue.
final class AdProofs {
private let adUnit: String
private let queue = DispatchQueue(label: "ad-proofs") // serial: keeps the three in order
private var adSessionId: String? // DEA's id for this ad, from the impression's answer
private var hasClicked = false
init(adUnit: String) {
self.adUnit = adUnit
}
/// The ad is on screen.
func shown() {
queue.async { [self] in
// Nothing is signed before the session is sufficient, and DEA refuses a proof
// from a device key it has not registered.
guard let dea = Dea.sdk,
let result = (try? dea.getClassification())?.result,
result != .insufficient,
dea.attest().verdict == .passed
else { return }
let outcome = dea.sendProof(event: .adImpression, sessionId: "", adUnit: adUnit)
if outcome.accepted, let id = outcome.sessionId {
adSessionId = id
} else {
print("DEA impression not recorded: \(outcome.said ?? "")")
}
}
}
/// The user clicked the ad. Only the first click is sent.
func clicked() {
queue.async { [self] in
guard let dea = Dea.sdk, let id = adSessionId, !hasClicked else { return }
hasClicked = true
let outcome = dea.sendProof(event: .adClick, sessionId: id, adUnit: adUnit)
if !outcome.accepted {
print("DEA click not recorded: \(outcome.said ?? "")")
}
}
}
/// The ad is gone: removed, replaced or dismissed.
func gone() {
queue.async { [self] in
guard let dea = Dea.sdk, let id = adSessionId else { return }
adSessionId = nil // nothing more is sent for this ad
let outcome = dea.sendProof(
event: .adEnded, sessionId: id, adUnit: adUnit, update: true)
if !outcome.accepted {
print("DEA end not recorded: \(outcome.said ?? "")")
}
}
}
}
Create one AdProofs per ad, and call its methods from your ad SDK's callbacks:
let ad = AdProofs(adUnit: "YOUR_AD_UNIT")
ad.shown() // your ad SDK reports the ad on screen
ad.clicked() // it reports a click
ad.gone() // the ad is removed or dismissed
The next ad is a new AdProofs, and its impression opens a new ad session with a new id.
API¶
| Member | Returns | Description |
|---|---|---|
sendProof(event:sessionId:attestation:adUnit:appScopedUid:extra:update:) |
RecordOutcome |
Signs and sends one proof, and says what happened. Blocking |
record(event:sessionId:attestation:adUnit:appScopedUid:extra:update:) |
Bool |
The same, answering only whether DEA accepted it. Blocking |
getProof(event:sessionId:attestation:adUnit:appScopedUid:) |
SignedProof? |
Signs one proof without sending it. Blocking |
ProofEvent: .adImpression, .adClick, .adEnded.
RecordOutcome:
| Field | Type | Description |
|---|---|---|
accepted |
Bool |
Whether DEA accepted the proof |
signed |
SignedProof? |
The proof that was signed, or nil when nothing was |
isSigned |
Bool |
Whether a proof was signed |
said |
String? |
What DEA answered, or why nothing was sent. nil when DEA accepted it with an empty answer |
sessionId |
String? |
The ad session id DEA answered with. Set by an accepted impression |
For SignedProof and the parameters, see the API Reference.