> ## Documentation Index
> Fetch the complete documentation index at: https://playcamp.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Go SDK

> Official Go SDK for PlayCamp API integration

The official Go SDK (`github.com/playcamp/playcamp-go-sdk`) provides a type-safe way to integrate with the PlayCamp API. It handles authentication, pagination, retries, and error handling with zero external dependencies.

## Installation

```bash theme={null}
go get github.com/playcamp/playcamp-go-sdk
```

**Requirements:** Go >= 1.21

## Quick Start

```go theme={null}
import playcamp "github.com/playcamp/playcamp-go-sdk"

server, err := playcamp.NewServer("your_server_key:your_secret",
    playcamp.WithEnvironment(playcamp.EnvironmentSandbox),
)
if err != nil {
    log.Fatal(err)
}

// Create a boost
sponsor, err := server.Sponsors.Create(ctx, playcamp.CreateSponsorParams{
    UserID:     "user_12345",
    CreatorKey: "ABC12",
})

// Register a payment
payment, err := server.Payments.Create(ctx, playcamp.CreatePaymentParams{
    UserID:        "user_12345",
    TransactionID: "txn_abc123",
    ProductID:     "gem_pack_100",
    Amount:        9900,
    Currency:      "KRW",
    Platform:      playcamp.PaymentPlatformAndroid,
    DistributionType: playcamp.String("MOBILE_STORE"),
    PurchasedAt:   time.Now(),
})
```

## Client vs Server

The SDK provides two constructors depending on your API key type:

| Constructor | API Key    | Access     | Use Case                    |
| ----------- | ---------- | ---------- | --------------------------- |
| `NewClient` | Client Key | Read-only  | Game client, public queries |
| `NewServer` | Server Key | Read/Write | Game server, all operations |

```go theme={null}
import playcamp "github.com/playcamp/playcamp-go-sdk"

// Read-only access (Client Key)
client, err := playcamp.NewClient("your_client_key:your_secret")

// Full access (Server Key)
server, err := playcamp.NewServer("your_server_key:your_secret")
```

<Warning>
  Never use `NewServer` or expose your Server Key on the client side.
</Warning>

### Client Resources

| Resource    | Methods                                                          |
| ----------- | ---------------------------------------------------------------- |
| `Campaigns` | `List()`, `ListAll()`, `Get()`, `GetCreators()`, `GetPackages()` |
| `Creators`  | `Get()`, `Search()`                                              |
| `Coupons`   | `Validate()`                                                     |
| `Sponsors`  | `Get()`                                                          |

### Server Resources

| Resource           | Methods                                                                               |
| ------------------ | ------------------------------------------------------------------------------------- |
| `Campaigns`        | `List()`, `ListAll()`, `Get()`, `GetCreators()`                                       |
| `Creators`         | `Get()`, `Search()`, `GetCoupons()`                                                   |
| `Coupons`          | `Validate()`, `Redeem()`, `GetUserHistory()`, `ListAllUserHistory()`                  |
| `Sponsors`         | `Create()`, `GetByUser()`, `Update()`, `Delete()`, `GetHistory()`, `ListAllHistory()` |
| `Payments`         | `Create()`, `CreateBulk()`, `Get()`, `ListByUser()`, `ListAllByUser()`, `Refund()`    |
| `PlaytimeSessions` | `Create()`, `CreateBulk()`                                                            |
| `Webhooks`         | `List()`, `Create()`, `Update()`, `Delete()`, `GetLogs()`, `Test()`                   |
| `Webview`          | `CreateOTT()`                                                                         |

## Configuration

```go theme={null}
server, err := playcamp.NewServer("key:secret",
    playcamp.WithEnvironment(playcamp.EnvironmentSandbox), // default: EnvironmentLive
    playcamp.WithTimeout(30 * time.Second),                // default: 30s
    playcamp.WithTestMode(true),                           // default: false
    playcamp.WithMaxRetries(3),                            // default: 3
    playcamp.WithDebug(playcamp.DebugOptions{              // default: disabled
        Enabled:         true,
        LogRequestBody:  true,
        LogResponseBody: true,
    }),
)
```

### Environments

| Environment          | URL                                   | Purpose              |
| -------------------- | ------------------------------------- | -------------------- |
| `EnvironmentSandbox` | `https://sandbox-sdk-api.playcamp.io` | Development/Testing  |
| `EnvironmentLive`    | `https://sdk-api.playcamp.io`         | Production (default) |

You can also specify a custom URL directly:

```go theme={null}
server, err := playcamp.NewServer("key:secret",
    playcamp.WithBaseURL("http://localhost:3003"), // Overrides environment setting
)
```

### Pointer Helpers

Use built-in helpers for optional fields:

```go theme={null}
playcamp.String("value")   // *string
playcamp.Int(10)           // *int
playcamp.Bool(true)        // *bool
playcamp.Float64(99.99)    // *float64
```

## Pagination

List endpoints return paginated results:

```go theme={null}
// Get a single page
result, err := server.Campaigns.List(ctx, &playcamp.PaginationOptions{
    Page:  playcamp.Int(1),
    Limit: playcamp.Int(20),
})
// result.Data, result.Pagination, result.HasNextPage
```

Use the iterator to automatically page through all results:

```go theme={null}
iterator := server.Campaigns.ListAll(&playcamp.PaginationOptions{
    Limit: playcamp.Int(50),
})

for iterator.Next(ctx) {
    campaign := iterator.Item()
    fmt.Println(campaign.CampaignID)
    iterator.Advance()
}

if err := iterator.Err(); err != nil {
    log.Fatal(err)
}
```

## Error Handling

The SDK returns typed errors for different scenarios:

```go theme={null}
import "errors"

campaign, err := server.Campaigns.Get(ctx, "invalid_id")
if err != nil {
    var notFoundErr *playcamp.NotFoundError
    var authErr     *playcamp.AuthError
    var rateLimitErr *playcamp.RateLimitError
    var networkErr  *playcamp.NetworkError
    var apiErr      *playcamp.APIError

    switch {
    case errors.As(err, &notFoundErr):
        fmt.Println("Campaign not found")
    case errors.As(err, &authErr):
        fmt.Println("Invalid API key")
    case errors.As(err, &rateLimitErr):
        fmt.Println("Rate limited:", rateLimitErr.Message)
    case errors.As(err, &networkErr):
        fmt.Println("Network error:", networkErr.Message)
    case errors.As(err, &apiErr):
        fmt.Printf("API error [%d]: %s\n", apiErr.StatusCode, apiErr.Message)
    default:
        fmt.Println("Unexpected error:", err)
    }
}
```

| Error Type             | HTTP Status | Description                      |
| ---------------------- | ----------- | -------------------------------- |
| `BadRequestError`      | 400         | Malformed request                |
| `AuthError`            | 401         | Authentication failed            |
| `ForbiddenError`       | 403         | Insufficient permissions         |
| `NotFoundError`        | 404         | Resource not found               |
| `ConflictError`        | 409         | Resource conflict (duplicate)    |
| `ValidationError`      | 422         | Server-side validation failed    |
| `RateLimitError`       | 429         | Rate limit exceeded              |
| `NetworkError`         | —           | Network/timeout error            |
| `InputValidationError` | —           | Client-side parameter validation |

## Webhook Verification

The SDK provides the `webhookutil` package for verifying incoming webhook signatures:

```go theme={null}
import "github.com/playcamp/playcamp-go-sdk/webhookutil"

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)

    result := webhookutil.Verify(webhookutil.VerifyOptions{
        Payload:   body,
        Signature: r.Header.Get("X-Webhook-Signature"),
        Secret:    "your_webhook_secret",
        Tolerance: 300, // Max age in seconds (default: 300)
    })

    if !result.Valid {
        http.Error(w, result.Error, http.StatusUnauthorized)
        return
    }

    for _, event := range result.Payload.Events {
        switch event.Event {
        case playcamp.WebhookEventCouponRedeemed:
            var data playcamp.CouponRedeemedData
            json.Unmarshal(event.Data, &data)
            fmt.Printf("Coupon redeemed: %s\n", data.CouponCode)
        case playcamp.WebhookEventPaymentCreated:
            var data playcamp.PaymentCreatedData
            json.Unmarshal(event.Data, &data)
            fmt.Printf("Payment created: %s\n", data.TransactionID)
        case playcamp.WebhookEventSponsorCreated:
            var data playcamp.SponsorCreatedData
            json.Unmarshal(event.Data, &data)
            fmt.Printf("Boost created: %s\n", data.UserID)
        }
    }

    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
```

For testing, you can generate signatures locally:

```go theme={null}
import "github.com/playcamp/playcamp-go-sdk/webhookutil"

payload := []byte(`{"events":[{"event":"coupon.redeemed","timestamp":"2024-01-15T10:30:00Z","data":{"couponCode":"TEST","userId":"user_123","usageId":1,"reward":[]}}]}`)

signature := webhookutil.ConstructSignature(payload, "your_webhook_secret", nil)
```

See the [Webhook Events](/guides/developers/game-integration/webhook) page for more details on webhook event types and payloads.

## Example Project

For a complete working example with all API endpoints, see the [playcamp-go-sdk-example](https://github.com/PlayCamp/playcamp-go-sdk-example) repository.
