Skip to main content

Custom Provider

Implement the Provider interface to send events to any backend — internal databases, custom HTTP APIs, or analytics platforms not yet supported natively.

Implementing the interface (Go)

package myprovider

import (
"context"
"github.com/dejanradmanovic/event-spec/analytics"
"github.com/dejanradmanovic/event-spec/provider"
"github.com/dejanradmanovic/event-spec/hooks"
)

type MyProvider struct{}

func New() *MyProvider { return &MyProvider{} }

func (p *MyProvider) Metadata() provider.ProviderMetadata {
return provider.ProviderMetadata{Name: "my-provider", Version: "1.0.0"}
}

func (p *MyProvider) Hooks() []hooks.Hook { return nil }

func (p *MyProvider) Track(ctx context.Context, msg provider.TrackMessage) error {
// Send msg to your backend
return nil
}

func (p *MyProvider) Identify(ctx context.Context, msg provider.IdentifyMessage) error {
return provider.ErrUnsupportedOperation // if not supported
}

func (p *MyProvider) Group(ctx context.Context, msg provider.GroupMessage) error {
return provider.ErrUnsupportedOperation
}

func (p *MyProvider) Page(ctx context.Context, msg provider.PageMessage) error {
return provider.ErrUnsupportedOperation
}

func (p *MyProvider) Alias(ctx context.Context, msg provider.AliasMessage) error {
return provider.ErrUnsupportedOperation
}

func (p *MyProvider) Flush(ctx context.Context) error { return nil }
func (p *MyProvider) Shutdown(ctx context.Context) error { return nil }

Implementing the interface (Kotlin)

import io.eventspec.analytics.*

class MyProvider : Provider {
override fun metadata() = ProviderMetadata(
name = "my-provider",
version = "1.0.0",
capabilities = ProviderCapabilities(
track = true, identify = false, group = false, page = false, alias = false,
),
)

override fun hooks(): List<Hook> = emptyList()

override suspend fun track(msg: TrackMessage) {
// Send msg to your backend
}

override suspend fun identify(msg: IdentifyMessage) {
throw UnsupportedOperationException("identify")
}

override suspend fun group(msg: GroupMessage) {
throw UnsupportedOperationException("group")
}

override suspend fun page(msg: PageMessage) {
throw UnsupportedOperationException("page")
}

override suspend fun alias(msg: AliasMessage) {
throw UnsupportedOperationException("alias")
}

override suspend fun flush() {}
override suspend fun shutdown() {}
}

Implementing the interface (TypeScript)

import type {
Provider,
ProviderMetadata,
TrackMessage,
IdentifyMessage,
GroupMessage,
PageMessage,
AliasMessage,
} from '@dejanradmanovic/event-spec-api';

export class MyProvider implements Provider {
readonly metadata: ProviderMetadata = {
name: 'my-provider',
version: '1.0.0',
};

async track(ctx: unknown, msg: TrackMessage): Promise<void> {
// Send to your backend
}

async identify(ctx: unknown, msg: IdentifyMessage): Promise<void> {
throw new Error('unsupported');
}

async group(ctx: unknown, msg: GroupMessage): Promise<void> {
throw new Error('unsupported');
}

async page(ctx: unknown, msg: PageMessage): Promise<void> {
throw new Error('unsupported');
}

async alias(ctx: unknown, msg: AliasMessage): Promise<void> {
throw new Error('unsupported');
}

async flush(ctx: unknown): Promise<void> {}
async shutdown(ctx: unknown): Promise<void> {}
}

Important: unsupported operations

Return provider.ErrUnsupportedOperation (Go) or throw new Error('unsupported') (TypeScript) for operations your provider doesn't support. Never silently return nil / void — that would look like a successful delivery.

Using built-in infrastructure

You can use the shared transport, queue, and rate-limiter instead of writing your own:

type MyProvider struct {
queue *provider.EventQueue
transport *provider.Transport
}

func New(cfg provider.ProviderConfig) (*MyProvider, error) {
transport, err := provider.NewTransport(cfg)
if err != nil {
return nil, err
}
queue := provider.NewEventQueue(provider.QueueConfig{
MaxSize: cfg.MaxQueueSize,
FlushInterval: cfg.FlushInterval,
BatchSize: cfg.BatchSize,
OnFlush: func(batch []provider.QueuedEvent) { /* send batch */ },
})
return &MyProvider{queue: queue, transport: transport}, nil
}

Provider capabilities

Declare what your provider supports so the runtime can produce accurate Dropped outcomes:

func (p *MyProvider) Metadata() provider.ProviderMetadata {
return provider.ProviderMetadata{
Name: "my-provider",
Version: "1.0.0",
Capabilities: provider.ProviderCapabilities{
Track: true,
Identify: false,
Group: false,
Page: false,
Alias: false,
},
}
}

Testing your provider

Use testutil.CaptureProvider to verify your provider receives the right messages in integration tests, and write unit tests that mock the HTTP layer.

Contributing

If your provider could be useful to others, consider contributing it to the main repository. See Contributing — Adding Providers.