Complete reference for all Kafka events published by the Ads Service. Subscribe to these topics to react to campaign lifecycle changes, ad delivery events, and moderation updates in real time.
The Ads Service publishes events to Amazon MSK (Kafka) using the CloudEvents 1.0 specification. All events are partitioned by tenant_id to ensure ordered processing per tenant.
ads for events from ads-svccampaign, broadcast, sphere)created, delivered, requested)Complete list of topics published by ads-svc.
| Topic | Description | Trigger | Partition Key |
|---|---|---|---|
| eventzr.ads.campaign.created.v1 | A new ad campaign was created | POST /ads/v1/campaigns | tenant_id |
| eventzr.ads.campaign.updated.v1 | An existing campaign was modified | PATCH /ads/v1/campaigns/:id | tenant_id |
| eventzr.ads.campaign.deleted.v1 | A campaign was deleted | DELETE /ads/v1/campaigns/:id | tenant_id |
| eventzr.ads.broadcast.requested.v1 | An ad broadcast was requested for delivery | Campaign launch or scheduled delivery | tenant_id |
| eventzr.ads.broadcast.delivered.v1 | An ad broadcast was successfully delivered | Ad delivery confirmation from serving pipeline | tenant_id |
| eventzr.ads.sphere.placement.v1 | An ad was placed in a Sphere community context | Ad served within sphere-svc integration | tenant_id |
| eventzr.ads.search.promoted.v1 | A promoted listing was served in search results | Promoted result delivered via search-svc integration | tenant_id |
| eventzr.ads.pricing.updated.v1 | Ad pricing or bid strategy was updated | Bid or budget change on a campaign | tenant_id |
| eventzr.ads.campaign.review.requested.v1 | A campaign or creative was submitted for moderation review | Creative submitted for approval | tenant_id |
| eventzr.ads.campaign.review.completed.v1 | Moderation review for a campaign or creative was completed | Approve or reject action on creative review | tenant_id |
Every Kafka event follows the CloudEvents 1.0 specification. The envelope wraps the domain-specific payload in a standardized structure.
{
"specversion": "1.0",
"id": "evt_unique_uuid",
"source": "eventzr.ads-svc",
"type": "eventzr.ads.campaign.created.v1",
"subject": "cmp_xyz789",
"time": "2026-03-09T10:00:00.000Z",
"datacontenttype": "application/json",
"tenantid": "<tenant-uuid>",
"data": {
// Domain-specific payload (see examples below)
}
}{
"id": "cmp_xyz789",
"tenantId": "<tenant-uuid>",
"name": "Summer Event Promo 2026",
"type": "display",
"status": "draft",
"budgetType": "daily",
"budgetAmount": 5000,
"currency": "USD",
"startDate": "2026-06-01T00:00:00Z",
"endDate": "2026-08-31T23:59:59Z",
"bidStrategy": "maximize_clicks",
"targetingAudienceId": "aud_abc123",
"createdBy": "usr_owner123",
"createdAt": "2026-03-09T10:00:00Z"
}{
"id": "cmp_xyz789",
"tenantId": "<tenant-uuid>",
"changes": {
"budgetAmount": { "from": 5000, "to": 7500 },
"status": { "from": "draft", "to": "active" }
},
"updatedBy": "usr_owner123",
"updatedAt": "2026-03-09T12:00:00Z"
}{
"broadcastId": "brd_abc123",
"tenantId": "<tenant-uuid>",
"campaignId": "cmp_xyz789",
"creativeId": "cre_pqr321",
"audienceId": "aud_abc123",
"channel": "display",
"scheduledAt": "2026-03-10T08:00:00Z",
"estimatedReach": 15000,
"requestedBy": "usr_owner123",
"requestedAt": "2026-03-09T12:30:00Z"
}{
"broadcastId": "brd_abc123",
"tenantId": "<tenant-uuid>",
"campaignId": "cmp_xyz789",
"impressionsDelivered": 12450,
"clicksDelivered": 312,
"costIncurred": 156.25,
"currency": "USD",
"deliveredAt": "2026-03-10T08:05:00Z"
}{
"reviewId": "rev_abc123",
"tenantId": "<tenant-uuid>",
"campaignId": "cmp_xyz789",
"creativeId": "cre_pqr321",
"submittedBy": "usr_owner123",
"submittedAt": "2026-03-09T11:00:00Z",
"reviewType": "creative_approval",
"priority": "normal"
}{
"reviewId": "rev_abc123",
"tenantId": "<tenant-uuid>",
"campaignId": "cmp_xyz789",
"creativeId": "cre_pqr321",
"decision": "approved",
"reviewedBy": "usr_moderator456",
"reviewedAt": "2026-03-09T13:00:00Z",
"reason": null,
"notes": "Creative meets all ad policy requirements"
}To consume ads-svc events in your NestJS microservice, use the @eventzr/kafka package. Below is a complete consumer example.
import { Injectable } from '@nestjs/common';
import { KafkaConsumer, OnKafkaEvent } from '@eventzr/kafka';
import { Logger } from '@eventzr/logger';
@Injectable()
export class AdsCampaignConsumer {
private readonly logger = new Logger(AdsCampaignConsumer.name);
@OnKafkaEvent('eventzr.ads.campaign.created.v1')
async handleCampaignCreated(event: CloudEvent<CampaignCreatedPayload>): Promise<void> {
const { data, tenantid } = event;
this.logger.log(`Campaign created: ${data.id} for tenant ${tenantid}`);
// Process the event (e.g., create matching records, send notifications)
await this.setRLSContext(tenantid);
await this.campaignRepo.save({
externalCampaignId: data.id,
name: data.name,
tenantId: tenantid,
});
}
@OnKafkaEvent('eventzr.ads.broadcast.delivered.v1')
async handleBroadcastDelivered(event: CloudEvent<BroadcastDeliveredPayload>): Promise<void> {
const { data, tenantid } = event;
this.logger.log(`Broadcast delivered: ${data.broadcastId}, impressions: ${data.impressionsDelivered}`);
// Update analytics, trigger notifications, etc.
}
@OnKafkaEvent('eventzr.ads.campaign.review.completed.v1')
async handleReviewCompleted(event: CloudEvent<ReviewCompletedPayload>): Promise<void> {
const { data, tenantid } = event;
if (data.decision === 'rejected') {
this.logger.warn(`Creative rejected: ${data.creativeId}, reason: ${data.reason}`);
// Notify campaign owner via notify-svc
}
}
}Register the Kafka consumer module in your service's app module. The KafkaBrokerModule handles MSK IAM authentication and consumer group management.
import { Module } from '@nestjs/common';
import { KafkaBrokerModule } from '@eventzr/kafka';
import { AdsCampaignConsumer } from './consumers/ads-campaign.consumer';
@Module({
imports: [
KafkaBrokerModule.forRoot({
brokers: process.env.KAFKA_BROKERS?.split(',') ?? [],
groupId: 'your-service-ads-consumer',
topics: [
'eventzr.ads.campaign.created.v1',
'eventzr.ads.campaign.updated.v1',
'eventzr.ads.campaign.deleted.v1',
'eventzr.ads.broadcast.delivered.v1',
'eventzr.ads.campaign.review.completed.v1',
],
}),
],
providers: [AdsCampaignConsumer],
})
export class AppModule {}Events may be delivered more than once (at-least-once delivery). Use the event id field as a deduplication key. Store processed event IDs and skip duplicates.
New fields may be added to event payloads in future versions. Use permissive deserialization and ignore unknown fields rather than failing.
Each subscribed topic creates consumer overhead. Only subscribe to the specific events your service needs to react to.
The tenantid field in the CloudEvents envelope must be used to call setRLSContext() before any database query in the consumer handler.
If event processing fails after retries, route the event to a dead-letter topic for manual investigation. Never silently drop events.
Track Kafka consumer group lag metrics to detect processing bottlenecks. Alert when lag exceeds acceptable thresholds for your use case.