checkslip

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 9, 2026 License: MIT Imports: 23 Imported by: 0

README

Checkslip Go SDK

ภาษาไทย · API docs · API reference · Go reference

The official Go client for the Checkslip API.

Checkslip looks up a Thai bank transfer from its slip. You send the QR text printed on the slip, or a picture of the slip, and Checkslip asks the sending bank for the transfer it holds and returns that data. Checkslip reports what the bank has. Whether to accept the payment is up to you, so compare the amount, the receiver and the time with your order.

The SDK needs Go 1.22 or later and has no dependencies outside the standard library.

Install

go get github.com/Checkslip/sdk-go

Quick start

Create an API key in the Checkslip dashboard. Keys start with csk_. Keep the key out of your source code and pass it in through the environment or a secret manager.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/Checkslip/sdk-go"
)

func main() {
	client := checkslip.NewClient(os.Getenv("CHECKSLIP_API_KEY"))

	res, err := client.Slips.Lookup(context.Background(), &checkslip.LookupParams{
		QR:              "00330006000001010306902126302105340875102TH9104F86F",
		ClientReference: "order-1001", // optional, comes back in history and webhooks
	})
	if err != nil {
		log.Fatal(err)
	}

	switch res.Outcome {
	case checkslip.OutcomeFound:
		fmt.Println("amount:", res.Slip.Amount)
		fmt.Println("sent at:", res.Slip.TransferredAt)
		fmt.Println("receiver account:", *res.Slip.Receiver.Account)
	case checkslip.OutcomeNotFound:
		fmt.Println("the bank has no transfer for this reference")
	case checkslip.OutcomeDuplicate:
		fmt.Println("looked up before, first at", res.DuplicateOf.CheckedAt)
	case checkslip.OutcomePending:
		fmt.Println("the bank does not show it yet; ask again after", res.RetryAfter)
	}
}

Only a FOUND lookup uses a credit.

To send an image instead of the QR text, set Image on LookupParams instead of QR. JPEG, PNG and WebP up to 10 MB are accepted, and Checkslip does not store the image. Setting both, or neither, is an error the client reports before it sends anything.

f, err := os.Open("slip.jpg")
if err != nil {
	return err
}
defer f.Close()

res, err := client.Slips.Lookup(ctx, &checkslip.LookupParams{Image: f})

A PENDING lookup is settled later. Read it with client.Slips.Get(ctx, res.RequestID) after res.RetryAfter, or wait for the slip.checked webhook.

What the client covers

Service Methods
client.Slips Lookup, Get, List, ListAll, Counts, and Exports (Create, Get, DownloadPart)
client.Banks List
client.Billing Balance.Get, Info (Get, Update), Packages.List, Ledger (List, ListAll), Orders (List, ListAll, Get, Sync), Receipts (List, ListAll, Get)
client.Usage Get
client.Audit Events (List, ListAll, Get), Requests (List, ListAll, Get), Checkpoints (List, ListAll, ListLatest), Chain.Check, Exports (List, ListAll, Create, Get, Download), SigningKeys.Get
client.Webhooks Verify, EventTypes.List, Endpoints (List, ListAll, Create, Get, Update, Delete, Recover, RotateSecret, SendTest), Deliveries (List, ListAll, Get, Redeliver)

The same names, in the same shape, are in the JavaScript, PHP and Rust clients.

Every method takes a context.Context first. Use it for deadlines and cancellation.

Errors

Any response with status 400 or above comes back as a *checkslip.Error. It carries the RFC 9457 problem details the API sent.

res, err := client.Slips.Lookup(ctx, params)
if err != nil {
	var apiErr *checkslip.Error
	if errors.As(err, &apiErr) {
		log.Printf("status=%d code=%s title=%q request=%s",
			apiErr.StatusCode, apiErr.Code, apiErr.Title, apiErr.RequestID)
	}

	switch {
	case checkslip.HasCode(err, checkslip.CodeInvalidQR):
		// the QR could not be read as a slip QR
	case checkslip.HasCode(err, checkslip.CodeTooOld):
		// older than the bank lets us look back
	case checkslip.IsNoCredit(err):
		// top up credits
	case checkslip.IsRateLimited(err):
		// wait apiErr.RetryAfter
	}
	return err
}

Quote RequestID when you contact Checkslip support. A QR that cannot be used (INVALID_QR, QR_TAMPERED, TOO_OLD, INVALID_FILE) returns an error and uses no credit.

The helpers are IsRateLimited, IsUnauthenticated, IsForbidden, IsNotFound, IsNoCredit, IsValidation, IsServerError, HasCode and AsError. A failed connection or a cancelled context is returned as a plain error, not as *checkslip.Error.

Retries

The client sends a failed request again, up to 2 more times by default, waiting longer each time (from 0.5 s, doubling up to 8 s, with random jitter).

  • 429 Too Many Requests is always retried after the Retry-After time the API sent.
  • 500, 502, 503, 504 and network failures are retried only when sending the request twice cannot do the work twice: GET, PUT and DELETE requests, and any request with an Idempotency-Key.
  • 409 IDEMPOTENCY_IN_FLIGHT is retried with the same key while the first request is still running.

The client does not wait out a Retry-After longer than 60 seconds or longer than the time left on your context. It returns the error at once, with RetryAfter set.

Idempotency

Slips.Lookup always sends an Idempotency-Key. So do the calls the API requires one for: creating an evidence file, recovering, rotating a secret, sending a test and redelivering webhooks. When you do not pass a key, the client makes a random one and reuses it on its own retries. A lookup sent again with the same key returns the first answer and is never charged twice.

Pass your own key when your program may resend the same request after a restart, for example a key built from your order number:

res, err := client.Slips.Lookup(ctx, params,
	checkslip.WithIdempotencyKey("order-1001-slip-1"))

The key that was sent is in res.IdempotencyKey, and in apiErr.IdempotencyKey when the call failed. Keys are 1 to 255 printable ASCII characters.

Pagination

List methods return one Page. Pass its NextCursor back in ListOptions.Cursor for the next page, or let ListAll do it:

it := client.Slips.ListAll(ctx, &checkslip.ListSlipsParams{
	Outcome: []checkslip.Outcome{checkslip.OutcomeFound},
})
for it.Next() {
	rec := it.Current()
	fmt.Println(rec.RequestID, rec.CheckedAt)
}
if err := it.Err(); err != nil {
	return err
}

On Go 1.23 or later you can range over the iterator:

for rec, err := range client.Slips.ListAll(ctx, nil).All() {
	if err != nil {
		return err
	}
	fmt.Println(rec.RequestID)
}

it.Cursor() gives the cursor of the next page, so a long walk can stop and pick up later.

Webhooks

Checkslip signs webhook messages with Standard Webhooks v1. Each request has a webhook-id, a webhook-timestamp and a webhook-signature header. The signature is HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{raw body}, keyed with the endpoint secret (whsec_...).

Check every message before you act on it:

verifier, err := checkslip.NewWebhookVerifier(os.Getenv("CHECKSLIP_WEBHOOK_SECRET"))
if err != nil {
	log.Fatal(err)
}

http.HandleFunc("/hooks/checkslip", func(w http.ResponseWriter, r *http.Request) {
	ev, err := verifier.VerifyRequest(r)
	if err != nil {
		http.Error(w, "bad signature", http.StatusUnauthorized)
		return
	}
	if seenBefore(ev.ID) { // the same message can arrive more than once
		w.WriteHeader(http.StatusNoContent)
		return
	}
	if ev.Type == checkslip.EventSlipChecked {
		data, err := ev.SlipChecked()
		if err != nil {
			http.Error(w, "bad payload", http.StatusBadRequest)
			return
		}
		markOrder(data.ClientReference, data.Outcome, data.Slip)
	}
	w.WriteHeader(http.StatusNoContent)
})

VerifyRequest reads the raw body, so do not parse the JSON before it. It refuses a message whose timestamp is more than 5 minutes from your clock (SetTolerance changes that). Any 2xx answer counts as delivered; other answers are retried 8 times over about 27.5 hours, and 410 Gone turns the endpoint off.

After RotateSecret, the old secret keeps signing for 24 hours and each message carries both signatures. Pass both secrets while you switch:

verifier, err := checkslip.NewWebhookVerifier(newSecret, oldSecret)

If you already have the body and headers, verifier.Verify(header, body) and checkslip.VerifyWebhook(secret, header, body) do the same check.

Configuration

client := checkslip.NewClient(apiKey,
	checkslip.WithTimeout(10*time.Second),          // per attempt; default 30 s
	checkslip.WithMaxRetries(3),                    // default 2; 0 turns retries off
	checkslip.WithUserAgent("my-shop/1.4"),         // put in front of the SDK's User-Agent
	checkslip.WithHTTPClient(httpClient),           // your own proxy, TLS or tracing
	checkslip.WithBaseURL("http://127.0.0.1:8080"), // a local mock server
)

WithBaseURL accepts https, and plain http only for loopback addresses, so the API key never crosses a network in clear text. A bad option is reported by the first request. WithHeader adds a header to one call, for example a W3C traceparent.

A Client is safe to share between goroutines. Create one and reuse it.

Versioning

The SDK follows semantic versioning. Before v1.0.0, a minor release may change the API; each change is listed in CHANGELOG.md.

License

MIT. See LICENSE. To report a security issue, see SECURITY.md.

Documentation

Overview

Package checkslip is the official Go client for the Checkslip API (https://checkslip.com/docs).

Checkslip looks up a Thai bank transfer from its slip. Send the QR text on the slip, or a picture of the slip, and Checkslip asks the sending bank for the transfer it holds and returns that data. Whether to accept the payment is your decision: compare the amount, receiver and time with your order.

Getting started

client := checkslip.NewClient(os.Getenv("CHECKSLIP_API_KEY"))
res, err := client.Slips.Lookup(ctx, &checkslip.LookupParams{QR: qrText})
if err != nil {
	return err
}
switch res.Outcome {
case checkslip.OutcomeFound:
	fmt.Println(res.Slip.Amount, res.Slip.TransferredAt)
case checkslip.OutcomePending:
	// Read it later with client.Slips.Get(ctx, res.RequestID).
}

Errors

Every response with status 400 or above is returned as an *Error that holds the problem details the API sent (RFC 9457): StatusCode, Code, Title, Detail and RequestID. Use errors.As, AsError, HasCode or helpers such as IsRateLimited and IsNoCredit to tell failures apart.

Retries

The client sends a failed request again, up to WithMaxRetries times (2 by default), with exponential backoff and jitter:

  • 429 Too Many Requests is always retried, after the Retry-After the API asked for.
  • 500, 502, 503 and 504 and network failures are retried only when the request is safe to repeat: GET, PUT and DELETE, or any request that carries an Idempotency-Key.
  • 409 IDEMPOTENCY_IN_FLIGHT is retried with the same key.

A Retry-After longer than 60 seconds, or longer than the time left on the context, is not waited out; the *Error is returned with RetryAfter set.

Idempotency

Slips.Lookup and every call the API requires a key for send an Idempotency-Key. The client generates a random one unless you pass WithIdempotencyKey. A request sent again with the same key returns the first result, and a lookup is never charged twice.

Pagination

List methods return one Page. ListAll methods return an Iter that follows next_cursor across pages; on Go 1.23 or later, Iter.All works with range.

Webhooks

Messages follow Standard Webhooks v1. Check each one with WebhookVerifier.VerifyRequest before you act on it, and deduplicate on its webhook-id because a message can arrive more than once.

Index

Examples

Constants

View Source
const DefaultBaseURL = "https://api.checkslip.com"

DefaultBaseURL is the Checkslip API address used when WithBaseURL is not given.

View Source
const DefaultWebhookTolerance = 5 * time.Minute

DefaultWebhookTolerance is how far webhook-timestamp may be from the receiver's clock before a message is refused, to stop replays.

View Source
const MaxImageSize = 10 << 20

MaxImageSize is the largest slip image the API accepts, 10 MB.

View Source
const MaxWebhookBody = 1 << 20

MaxWebhookBody is the largest body VerifyRequest reads.

View Source
const Version = "0.1.0"

Version is the version of this SDK. It is sent in the User-Agent header. x-release-please-version

Variables

View Source
var (
	// ErrWebhookMissingHeaders means a Standard Webhooks header is absent.
	ErrWebhookMissingHeaders = errors.New("checkslip: webhook headers missing")
	// ErrWebhookTimestamp means webhook-timestamp is unreadable or outside
	// the tolerance.
	ErrWebhookTimestamp = errors.New("checkslip: webhook timestamp outside tolerance")
	// ErrWebhookSignature means no signature matched any secret.
	ErrWebhookSignature = errors.New("checkslip: webhook signature does not match")
)

Webhook verification errors. Compare with errors.Is. Answer 400 or 401 to a message that fails, and do not process it.

Functions

func HasCode

func HasCode(err error, code ErrorCode) bool

HasCode reports whether err is an API error with the given code.

func IsForbidden

func IsForbidden(err error) bool

IsForbidden reports whether the API key lacks the permission for the call (403).

func IsNoCredit

func IsNoCredit(err error) bool

IsNoCredit reports whether a lookup was refused because the account has no credit left or its credit expired (402).

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether the requested record does not exist for this account (404).

func IsRateLimited

func IsRateLimited(err error) bool

IsRateLimited reports whether the API answered 429 Too Many Requests. The error's RetryAfter says how long to wait.

func IsServerError

func IsServerError(err error) bool

IsServerError reports whether the API failed on its side (status 500 or above). The client already retried it when that was safe.

func IsUnauthenticated

func IsUnauthenticated(err error) bool

IsUnauthenticated reports whether the API key was missing, wrong or revoked (401).

func IsValidation

func IsValidation(err error) bool

IsValidation reports whether the request was rejected as malformed (400 or 422). Check Error.Code and Error.Fields for the reason.

func NewIdempotencyKey

func NewIdempotencyKey() string

NewIdempotencyKey returns a random UUID (version 4) to use as an Idempotency-Key. The client calls it for you when a method needs a key and you did not pass one with WithIdempotencyKey.

func VerifyWebhook

func VerifyWebhook(secret string, header http.Header, body []byte) error

VerifyWebhook checks one message against one secret with the default tolerance. It is a shortcut for NewWebhookVerifier(secret) followed by Verify.

Types

type APIRequestRecord

type APIRequestRecord struct {
	RequestID  string    `json:"request_id"`
	ReceivedAt time.Time `json:"received_at"`
	// LatencyMs is how long this request took, from receiving it to
	// answering it.
	LatencyMs int `json:"latency_ms"`
	// Channel may be nil.
	Channel    *Channel `json:"channel"`
	Method     string   `json:"method"`
	Route      string   `json:"route"`
	HTTPStatus int      `json:"http_status"`
	APIKeyID   *string  `json:"api_key_id,omitempty"`
	UserID     *string  `json:"user_id,omitempty"`
	// ClientIPPrefix is the caller's network (/24 or /48).
	ClientIPPrefix string `json:"client_ip_prefix,omitempty"`
	// ClientIPHMAC is a keyed hash of the full address, to match requests
	// from the same machine.
	ClientIPHMAC string       `json:"client_ip_hmac,omitempty"`
	UserAgent    string       `json:"user_agent,omitempty"`
	OutcomeCode  string       `json:"outcome_code,omitempty"`
	Fingerprint  *Fingerprint `json:"fingerprint,omitempty"`
}

APIRequestRecord is the record of one request this account sent. Records hold no names, phone numbers, emails or full account numbers.

type ActorType

type ActorType string

ActorType is who performed an action in the audit log.

const (
	ActorUser      ActorType = "USER"
	ActorAPIKey    ActorType = "API_KEY"
	ActorAssistant ActorType = "ASSISTANT"
	ActorStaff     ActorType = "STAFF"
	ActorSystem    ActorType = "SYSTEM"
)

Actor types.

type AttemptOutcome

type AttemptOutcome string

AttemptOutcome is the result of one HTTP attempt of a webhook delivery.

const (
	AttemptSuccess             AttemptOutcome = "SUCCESS"
	AttemptHTTPError           AttemptOutcome = "HTTP_ERROR"
	AttemptTimeout             AttemptOutcome = "TIMEOUT"
	AttemptConnectError        AttemptOutcome = "CONNECT_ERROR"
	AttemptTLSError            AttemptOutcome = "TLS_ERROR"
	AttemptRedirectNotFollowed AttemptOutcome = "REDIRECT_NOT_FOLLOWED"
	AttemptDestinationBlocked  AttemptOutcome = "DESTINATION_BLOCKED"
)

Attempt outcomes.

type AttemptTrigger

type AttemptTrigger string

AttemptTrigger says what started a webhook attempt.

const (
	TriggerSchedule  AttemptTrigger = "SCHEDULE"
	TriggerRedeliver AttemptTrigger = "REDELIVER"
	TriggerRecover   AttemptTrigger = "RECOVER"
)

Attempt triggers.

type AuditActor

type AuditActor struct {
	Type           ActorType `json:"type"`
	ID             *string   `json:"id,omitempty"`
	OnBehalfOfUser *string   `json:"on_behalf_of_user_id,omitempty"`
}

AuditActor is who performed an audited action.

type AuditCategory

type AuditCategory string

AuditCategory groups audit events.

const (
	AuditMoney         AuditCategory = "MONEY"
	AuditAccess        AuditCategory = "ACCESS"
	AuditDataChange    AuditCategory = "DATA_CHANGE"
	AuditSensitiveRead AuditCategory = "SENSITIVE_READ"
	AuditSecurity      AuditCategory = "SECURITY"
)

Audit categories.

type AuditEventRecord

type AuditEventRecord struct {
	EventID    string        `json:"event_id"`
	Category   AuditCategory `json:"category"`
	Outcome    AuditOutcome  `json:"outcome"`
	OccurredAt time.Time     `json:"occurred_at"`
	// RequestID is the API request that caused this event, if any.
	RequestID *string     `json:"request_id,omitempty"`
	Actor     AuditActor  `json:"actor"`
	Channel   Channel     `json:"channel"`
	Action    string      `json:"action"`
	Target    AuditTarget `json:"target"`
	// Before and After are the values before and after the change, as raw
	// JSON objects.
	Before        map[string]any `json:"before,omitempty"`
	After         map[string]any `json:"after,omitempty"`
	Reason        *string        `json:"reason,omitempty"`
	UndoOfEventID *string        `json:"undo_of_event_id,omitempty"`
}

AuditEventRecord is one action on the account, such as an API key created or a member removed.

type AuditEventsService

type AuditEventsService struct {
	// contains filtered or unexported fields
}

AuditEventsService reads account activity.

func (*AuditEventsService) Get

func (s *AuditEventsService) Get(ctx context.Context, eventID string, opts ...RequestOption) (*AuditEventRecord, error)

Get returns one activity record.

func (*AuditEventsService) List

List returns one page of account activity, newest first.

func (*AuditEventsService) ListAll

ListAll walks every activity record that matches params.

type AuditExportsService

type AuditExportsService struct {
	// contains filtered or unexported fields
}

AuditExportsService builds and downloads evidence files.

func (*AuditExportsService) Create

Create starts building an evidence file. It returns at once with State QUEUED; poll GetExport until it is READY or FAILED. The call always carries an Idempotency-Key, so a retry returns the same file.

func (*AuditExportsService) Download

func (s *AuditExportsService) Download(ctx context.Context, exportID string, opts ...RequestOption) (*EvidenceFile, error)

Download downloads a READY evidence file. Before it is READY the API answers 409; after ExpiresAt it answers 410. Every download is written to the account activity.

func (*AuditExportsService) Get

func (s *AuditExportsService) Get(ctx context.Context, exportID string, opts ...RequestOption) (*EvidenceExport, error)

Get returns the state of one evidence file.

func (*AuditExportsService) List

List returns one page of evidence files, newest first.

func (*AuditExportsService) ListAll

func (s *AuditExportsService) ListAll(ctx context.Context, params *ListOptions, opts ...RequestOption) *Iter[EvidenceExport]

ListAll walks every evidence file.

type AuditOutcome

type AuditOutcome string

AuditOutcome is how an audited action ended.

const (
	AuditSuccess AuditOutcome = "SUCCESS"
	AuditFailure AuditOutcome = "FAILURE"
	AuditDenied  AuditOutcome = "DENIED"
)

Audit outcomes.

type AuditRequestsService

type AuditRequestsService struct {
	// contains filtered or unexported fields
}

AuditRequestsService reads the account's request log.

func (*AuditRequestsService) Get

func (s *AuditRequestsService) Get(ctx context.Context, requestID string, opts ...RequestOption) (*APIRequestRecord, error)

Get returns one request record.

func (*AuditRequestsService) List

List returns one page of the requests this account sent, newest first.

func (*AuditRequestsService) ListAll

ListAll walks every request record that matches params.

type AuditService

type AuditService struct {

	// Events is account activity, such as API keys created or members removed.
	Events *AuditEventsService
	// Requests is the log of requests this account sent.
	Requests *AuditRequestsService
	// Exports builds evidence files for an auditor, a bank or a court.
	Exports *AuditExportsService
	// contains filtered or unexported fields
}

AuditService reads the account's request records and activity, and builds evidence files of them for an auditor, a bank or a court. It needs an API key with audit:read; evidence files also need audit:export.

type AuditTarget

type AuditTarget struct {
	Type string `json:"type"`
	ID   string `json:"id"`
}

AuditTarget is what an audited action was done to.

type Balance

type Balance struct {
	AccountID string `json:"account_id"`
	// Credits is the number of lookups left. One FOUND lookup uses one.
	Credits int64 `json:"credits"`
	// Low is true when Credits has fallen to Threshold or below. Show it as
	// it is rather than computing your own threshold.
	Low       bool           `json:"low"`
	Threshold int64          `json:"threshold"`
	Basis     ThresholdBasis `json:"threshold_basis"`
	// ExpiresAt is when the remaining credits expire, or nil.
	ExpiresAt   *time.Time `json:"expires_at"`
	UpdatedAt   time.Time  `json:"updated_at"`
	LastEntryID *string    `json:"last_entry_id"`
}

Balance is the account's credit balance.

type Bank

type Bank struct {
	// Code is Checkslip's short code, for example "KBANK".
	Code string `json:"code"`
	// BOTCode is the three-digit Bank of Thailand code, for example "004".
	BOTCode string `json:"bot_code"`
	// NameTH is the bank's Thai name, when known.
	NameTH *string `json:"name_th"`
	// NameEN is the bank's English name, when known.
	NameEN *string `json:"name_en"`
}

Bank identifies a Thai bank.

type BanksService

type BanksService struct {
	// contains filtered or unexported fields
}

BanksService lists the banks whose transfers can be looked up.

func (*BanksService) List

func (s *BanksService) List(ctx context.Context, opts ...RequestOption) ([]SupportedBank, error)

List returns the supported banks. It needs no API key.

type BillingBalanceService

type BillingBalanceService struct {
	// contains filtered or unexported fields
}

BillingBalanceService reads the credit balance.

func (*BillingBalanceService) Get

Get returns the remaining credits, their expiry and whether they are running low. It needs an API key with credit:read or billing:read.

type BillingInfo

type BillingInfo struct {
	BuyerType BuyerType `json:"buyer_type,omitempty"`
	Name      string    `json:"name,omitempty"`
	TaxID     string    `json:"tax_id,omitempty"`
	Branch    string    `json:"branch,omitempty"`
	Address   string    `json:"address,omitempty"`
	Email     string    `json:"email,omitempty"`
}

BillingInfo is the buyer shown on receipts.

type BillingInfoService

type BillingInfoService struct {
	// contains filtered or unexported fields
}

BillingInfoService reads and replaces the billing details on receipts.

func (*BillingInfoService) Get

Get returns the billing details used on receipts.

func (*BillingInfoService) Update

func (s *BillingInfoService) Update(ctx context.Context, info *BillingInfo, opts ...RequestOption) (*BillingInfo, error)

Update saves the buyer details printed on your next receipts. Receipts already issued keep theirs. The tax ID must be 13 digits. It needs an API key with billing:write.

type BillingLedgerService

type BillingLedgerService struct {
	// contains filtered or unexported fields
}

BillingLedgerService reads the credit ledger.

func (*BillingLedgerService) List

List returns one page of credit balance changes, newest first: purchases, lookups that used a credit, lookups that did not (with the reason in FreeReason) and credits that expired. One call covers at most 31 days.

func (*BillingLedgerService) ListAll

func (s *BillingLedgerService) ListAll(ctx context.Context, params *LedgerParams, opts ...RequestOption) *Iter[CreditEntry]

ListAll walks every ledger entry that matches params.

type BillingOrdersService

type BillingOrdersService struct {
	// contains filtered or unexported fields
}

BillingOrdersService reads orders and refreshes their payment result.

func (*BillingOrdersService) Get

func (s *BillingOrdersService) Get(ctx context.Context, orderID string, opts ...RequestOption) (*Order, error)

Get returns one credit order.

func (*BillingOrdersService) List

func (s *BillingOrdersService) List(ctx context.Context, params *OrdersParams, opts ...RequestOption) (*Page[Order], error)

List returns one page of credit orders, newest first.

func (*BillingOrdersService) ListAll

func (s *BillingOrdersService) ListAll(ctx context.Context, params *OrdersParams, opts ...RequestOption) *Iter[Order]

ListAll walks every order that matches params.

func (*BillingOrdersService) Sync

func (s *BillingOrdersService) Sync(ctx context.Context, orderID string, opts ...RequestOption) (*Order, error)

Sync asks the payment provider for the payment result of an order now instead of waiting for its notice, and returns the order. If it is paid, the credits arrive then. If the payment page was closed but the money has not arrived, the order shows Confirming. Asking again within a few seconds returns the same result.

type BillingPackagesService

type BillingPackagesService struct {
	// contains filtered or unexported fields
}

BillingPackagesService lists the credit packages on sale.

func (*BillingPackagesService) List

List returns every credit package with its price, credits and validity. One credit covers one transfer lookup.

type BillingReceiptsService

type BillingReceiptsService struct {
	// contains filtered or unexported fields
}

BillingReceiptsService reads receipts.

func (*BillingReceiptsService) Get

func (s *BillingReceiptsService) Get(ctx context.Context, receiptID string, opts ...RequestOption) (*Receipt, error)

Get returns one receipt.

func (*BillingReceiptsService) List

func (s *BillingReceiptsService) List(ctx context.Context, params *ListOptions, opts ...RequestOption) (*Page[Receipt], error)

List returns one page of receipts, newest first.

func (*BillingReceiptsService) ListAll

func (s *BillingReceiptsService) ListAll(ctx context.Context, params *ListOptions, opts ...RequestOption) *Iter[Receipt]

ListAll walks every receipt.

type BillingService

type BillingService struct {

	// Balance is the credit left on the account.
	Balance *BillingBalanceService
	// Info is the billing details printed on receipts.
	Info *BillingInfoService
	// List are the credit packages on sale.
	Packages *BillingPackagesService
	// List is every top-up, charge and expiry.
	Ledger *BillingLedgerService
	// List are orders for credit packages.
	Orders *BillingOrdersService
	// List are receipts for paid orders.
	Receipts *BillingReceiptsService
	// contains filtered or unexported fields
}

BillingService reads credits, orders, receipts and the billing details printed on receipts.

type BuyerType

type BuyerType string

BuyerType says whether the buyer on a receipt is a person or a company.

const (
	BuyerPerson  BuyerType = "PERSON"
	BuyerCompany BuyerType = "COMPANY"
)

Buyer types.

type Channel

type Channel string

Channel is where a request or action came from.

const (
	ChannelAPI       Channel = "API"
	ChannelDashboard Channel = "DASHBOARD"
	ChannelAssistant Channel = "ASSISTANT"
	ChannelStaff     Channel = "STAFF"
	ChannelSystem    Channel = "SYSTEM"
)

Channels.

type ChargedFilter

type ChargedFilter string

ChargedFilter selects lookups by whether they used a credit.

const (
	ChargedYes       ChargedFilter = "true"
	ChargedNo        ChargedFilter = "false"
	ChargedUnsettled ChargedFilter = "unsettled"
)

Charged filters for ListSlipsParams.Charged.

type Client

type Client struct {

	// Slips looks up slips and reads the lookup history.
	Slips *SlipsService
	// Billing reads credits, orders, receipts and billing details.
	Billing *BillingService
	// Usage reads lookup counts per day or month.
	Usage *UsageService
	// Banks lists the banks whose transfers can be looked up.
	Banks *BanksService
	// Webhooks manages webhook endpoints and their deliveries.
	Webhooks *WebhooksService
	// Audit reads request records and activity and builds evidence files.
	Audit *AuditService
	// contains filtered or unexported fields
}

Client talks to the Checkslip API. Create one with NewClient and share it: a Client is safe for concurrent use by multiple goroutines.

The API is grouped into services that hang off the Client, for example client.Slips.Lookup or client.Billing.Balance.

func NewClient

func NewClient(apiKey string, opts ...Option) *Client

NewClient returns a Client that authenticates with apiKey, a key that starts with "csk_". Create keys in the Checkslip dashboard.

NewClient does not contact the API. A bad option, such as an unparsable base URL, is reported by the first request.

Example
package main

import (
	"os"
	"time"

	checkslip "github.com/Checkslip/sdk-go"
)

func main() {
	client := checkslip.NewClient(os.Getenv("CHECKSLIP_API_KEY"),
		checkslip.WithTimeout(10*time.Second),
		checkslip.WithMaxRetries(3),
		checkslip.WithUserAgent("my-shop/1.4"),
	)
	_ = client
}

type CountBucket

type CountBucket string

CountBucket is the bucket size of SlipsService.Counts.

const (
	BucketMinute         CountBucket = "MINUTE"
	BucketFiveMinutes    CountBucket = "FIVE_MINUTE"
	BucketFifteenMinutes CountBucket = "FIFTEEN_MINUTE"
	BucketHour           CountBucket = "HOUR"
	BucketThreeHours     CountBucket = "THREE_HOUR"
	BucketSixHours       CountBucket = "SIX_HOUR"
	BucketTwelveHours    CountBucket = "TWELVE_HOUR"
	BucketDay            CountBucket = "DAY"
)

Count buckets.

type CountSeries

type CountSeries struct {
	Start  time.Time         `json:"start"`
	Counts map[Outcome]int64 `json:"counts"`
}

CountSeries is the count of one bucket, keyed by outcome.

type CreateEndpointParams

type CreateEndpointParams struct {
	// URL must be https without user:pass@, and every address its domain
	// resolves to must be public. Required.
	URL string `json:"url"`
	// Description is a note for people. Optional.
	Description string `json:"description,omitempty"`
	// EventTypes lists the messages to send. Required.
	EventTypes []EventType `json:"event_types"`
}

CreateEndpointParams is the input of WebhookEndpointsService.Create.

type CreateEvidenceExportParams

type CreateEvidenceExportParams struct {
	// FromDay and ToDay are UTC days, at most 31 days apart. Only closed
	// days can be exported: not today, and yesterday only from 00:10 UTC.
	FromDay Date `json:"from_day"`
	ToDay   Date `json:"to_day"`
	// Streams picks request records, activity or both. Empty means both.
	Streams []Stream `json:"streams,omitempty"`
}

CreateEvidenceExportParams is the input of AuditService.CreateExport.

type CreditEntry

type CreditEntry struct {
	ID string `json:"id"`
	// Delta is the change: positive for credits added, negative for used
	// or expired.
	Delta          int64        `json:"delta"`
	BalanceAfter   int64        `json:"balance_after"`
	ExpiresAtAfter *time.Time   `json:"expires_at_after,omitempty"`
	Reason         CreditReason `json:"reason"`
	FreeReason     FreeReason   `json:"free_reason,omitempty"`
	// RefType and RefID point at what caused the change, such as an order
	// or a lookup.
	RefType   string    `json:"ref_type,omitempty"`
	RefID     string    `json:"ref_id,omitempty"`
	CreatedAt time.Time `json:"created_at"`
}

CreditEntry is one change of the credit balance.

type CreditReason

type CreditReason string

CreditReason says why the credit balance changed.

const (
	CreditTesterGrant    CreditReason = "TESTER_GRANT"
	CreditPurchase       CreditReason = "PURCHASE"
	CreditSlipCharge     CreditReason = "SLIP_CHARGE"
	CreditSlipFree       CreditReason = "SLIP_FREE"
	CreditExpire         CreditReason = "EXPIRE"
	CreditStaffAdjust    CreditReason = "STAFF_ADJUST"
	CreditAccountDeleted CreditReason = "ACCOUNT_DELETED"
	CreditDevGrant       CreditReason = "DEV_GRANT"
)

Credit reasons.

type Date

type Date struct {
	Year  int
	Month time.Month
	Day   int
}

Date is a calendar day without a time or time zone, sent as "2006-01-02".

func DateOf

func DateOf(t time.Time) Date

DateOf returns the calendar day of t in t's location.

func ParseDate

func ParseDate(s string) (Date, error)

ParseDate parses a day in the form "2006-01-02".

func (Date) IsZero

func (d Date) IsZero() bool

IsZero reports whether d is the zero Date.

func (Date) MarshalText

func (d Date) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (Date) String

func (d Date) String() string

String returns the day as "2006-01-02".

func (*Date) UnmarshalText

func (d *Date) UnmarshalText(b []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

type Delivery

type Delivery struct {
	ID string `json:"id"`
	// MessageID is the webhook-id header of the message. It stays the same
	// on every attempt and resend; deduplicate on it.
	MessageID      string         `json:"message_id"`
	EndpointID     string         `json:"endpoint_id"`
	EventType      EventType      `json:"event_type"`
	Status         DeliveryStatus `json:"status"`
	AttemptCount   int            `json:"attempt_count"`
	NextAttemptAt  *time.Time     `json:"next_attempt_at"`
	LastAttemptAt  *time.Time     `json:"last_attempt_at"`
	LastHTTPStatus *int           `json:"last_http_status"`
	CreatedAt      time.Time      `json:"created_at"`
}

Delivery is one message sent, or being sent, to one endpoint.

type DeliveryAttempt

type DeliveryAttempt struct {
	AttemptNo   int            `json:"attempt_no"`
	Trigger     AttemptTrigger `json:"trigger,omitempty"`
	AttemptedAt time.Time      `json:"attempted_at"`
	Outcome     AttemptOutcome `json:"outcome"`
	HTTPStatus  *int           `json:"http_status"`
	DurationMS  int64          `json:"duration_ms"`
	// ResponseExcerpt is the start of your endpoint's response body.
	ResponseExcerpt *string `json:"response_excerpt"`
}

DeliveryAttempt is one HTTP attempt of a delivery.

type DeliveryDetail

type DeliveryDetail struct {
	Delivery
	// Payload is the message body, or nil once it has expired. Decode it
	// with ParseWebhookEvent.
	Payload          json.RawMessage   `json:"payload"`
	PayloadExpiresAt time.Time         `json:"payload_expires_at"`
	Attempts         []DeliveryAttempt `json:"attempts"`
}

DeliveryDetail is a delivery with its payload and every attempt.

type DeliveryStatus

type DeliveryStatus string

DeliveryStatus is the state of one webhook delivery.

const (
	DeliveryPending   DeliveryStatus = "PENDING"
	DeliveryRetrying  DeliveryStatus = "RETRYING"
	DeliverySucceeded DeliveryStatus = "SUCCEEDED"
	// DeliveryFailed means 8 attempts over about 27.5 hours got no 2xx, or
	// your endpoint answered 410 Gone.
	DeliveryFailed    DeliveryStatus = "FAILED"
	DeliveryCancelled DeliveryStatus = "CANCELLED"
)

Delivery statuses.

type DisabledReason

type DisabledReason string

DisabledReason says why a webhook endpoint stopped receiving messages.

const (
	// DisabledManual means someone set enabled to false.
	DisabledManual DisabledReason = "MANUAL"
	// DisabledGone means the endpoint answered 410 Gone.
	DisabledGone DisabledReason = "GONE"
	// DisabledFailing means every delivery failed for 5 days in a row.
	DisabledFailing DisabledReason = "FAILING"
)

Disabled reasons.

type DuplicateOf

type DuplicateOf struct {
	RequestID string    `json:"request_id"`
	CheckedAt time.Time `json:"checked_at"`
}

DuplicateOf points at the earlier lookup of the same transfer.

type Endpoint

type Endpoint struct {
	ID          string      `json:"id"`
	URL         string      `json:"url"`
	Description *string     `json:"description"`
	EventTypes  []EventType `json:"event_types"`
	Enabled     bool        `json:"enabled"`
	// DisabledReason says why Enabled is false, or is empty.
	DisabledReason DisabledReason `json:"disabled_reason"`
	// SecretHint is the end of the signing secret, to tell secrets apart.
	SecretHint string `json:"secret_hint"`
	// PreviousSecretExpiresAt is set during the 24 hours after a secret
	// rotation, while the old secret still signs messages.
	PreviousSecretExpiresAt *time.Time `json:"previous_secret_expires_at,omitempty"`
	CreatedAt               time.Time  `json:"created_at"`
	UpdatedAt               time.Time  `json:"updated_at"`
}

Endpoint is a URL of your system that receives webhook messages.

type EndpointWithSecret

type EndpointWithSecret struct {
	Endpoint
	// Secret signs every message to this endpoint ("whsec_..."). It is
	// returned only once, when the endpoint is created. Store it in your
	// secret manager.
	Secret string `json:"secret"`
}

EndpointWithSecret is a new endpoint together with its signing secret.

type Error

type Error struct {
	// StatusCode is the HTTP status, for example 422.
	StatusCode int
	// Code is the machine-readable error code, for example CodeInvalidQR.
	Code ErrorCode
	// Type is the problem type URI, a page that explains the error.
	Type string
	// Title is a short summary written for people. It may be in Thai.
	Title string
	// Detail explains this occurrence, when the API sends one.
	Detail string
	// Instance identifies this occurrence.
	Instance string
	// RequestID is the X-Request-Id of the response. Quote it when you
	// contact Checkslip support.
	RequestID string
	// RetryAfter is the wait the API asked for in its Retry-After header,
	// or zero.
	RetryAfter time.Duration
	// IdempotencyKey is the key the failed request carried, if any. Send
	// the request again with the same key (WithIdempotencyKey) to get the
	// first result instead of a second one.
	IdempotencyKey string
	// Fields lists the request fields that failed validation.
	Fields []FieldError
	// Body is the raw response body, kept for responses that are not
	// problem details.
	Body []byte
}

Error is returned for every API response with status 400 or above. The API sends errors as RFC 9457 problem details; Error holds their fields.

Use errors.As to reach it:

var apiErr *checkslip.Error
if errors.As(err, &apiErr) {
	log.Println(apiErr.StatusCode, apiErr.Code, apiErr.RequestID)
}
Example
package main

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"

	checkslip "github.com/Checkslip/sdk-go"
)

// fakeAPI stands in for api.checkslip.com so the examples run offline.
func fakeAPI() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/slips/lookup":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"request_id":"0192d4e0-7c1a-7b3e-9f00-1a2b3c4d5e6f","outcome":"FOUND","checked_at":"2026-10-29T10:41:02+07:00",
"slip":{"trans_ref":"630210534087","transferred_at":"2026-10-29T10:33:30+07:00","amount":{"satang":5000,"currency":"THB"},
"sender":{"bank":{"code":"KKP","bot_code":"069","name_th":null,"name_en":"Kiatnakin Phatra Bank"},"name_th":null,"name_en":"MR. SOMCHAI J","account":"0439"},
"receiver":{"kind":"BANK_ACCOUNT","bank":{"code":"GSB","bot_code":"030","name_th":null,"name_en":"Government Savings Bank"},"name_th":null,"name_en":"MISS SOMYING R","account":"9681"}}}`)
		case "/api/billing/balance":
			w.Header().Set("Content-Type", "application/problem+json")
			w.Header().Set("X-Request-Id", "0192f3a4-7b1c-7d2e-8f30-4a5b6c7d8e9f")
			w.WriteHeader(http.StatusForbidden)
			fmt.Fprint(w, `{"type":"https://checkslip.com/problems/forbidden","title":"Forbidden","status":403,"code":"FORBIDDEN","instance":"/api/billing/balance"}`)
		case "/api/slips":
			w.Header().Set("Content-Type", "application/json")
			if r.URL.Query().Get("cursor") == "" {
				fmt.Fprint(w, `{"items":[{"request_id":"r1","outcome":"FOUND","checked_at":"2026-11-01T09:00:00Z","channel":"API"}],"next_cursor":"p2"}`)
				return
			}
			fmt.Fprint(w, `{"items":[{"request_id":"r2","outcome":"NOT_FOUND","checked_at":"2026-11-01T08:00:00Z","channel":"API"}],"next_cursor":null}`)
		default:
			http.NotFound(w, r)
		}
	}))
}

func main() {
	srv := fakeAPI()
	defer srv.Close()
	client := checkslip.NewClient("csk_example", checkslip.WithBaseURL(srv.URL))

	_, err := client.Billing.Balance.Get(context.Background())
	var apiErr *checkslip.Error
	if errors.As(err, &apiErr) {
		fmt.Println(apiErr.StatusCode, apiErr.Code, apiErr.RequestID)
	}
	fmt.Println(checkslip.IsForbidden(err))
}
Output:
403 FORBIDDEN 0192f3a4-7b1c-7d2e-8f30-4a5b6c7d8e9f
true

func AsError

func AsError(err error) *Error

AsError returns the *Error inside err, or nil when err did not come from an API response.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

type ErrorCode

type ErrorCode string

ErrorCode is the machine-readable code of an API error, such as "RATE_LIMITED". Compare it with the Code constants. New codes may appear over time, so treat an unknown code like a generic failure of its status.

const (
	CodeValidationFailed       ErrorCode = "VALIDATION_FAILED"
	CodeInvalidCursor          ErrorCode = "INVALID_CURSOR"
	CodeUnauthenticated        ErrorCode = "UNAUTHENTICATED"
	CodeForbidden              ErrorCode = "FORBIDDEN"
	CodeRateLimited            ErrorCode = "RATE_LIMITED"
	CodeInternal               ErrorCode = "INTERNAL"
	CodeIdempotencyKeyRequired ErrorCode = "IDEMPOTENCY_KEY_REQUIRED"
	CodeIdempotencyKeyInvalid  ErrorCode = "IDEMPOTENCY_KEY_INVALID"
	CodeIdempotencyKeyReused   ErrorCode = "IDEMPOTENCY_KEY_REUSED"
	CodeIdempotencyInFlight    ErrorCode = "IDEMPOTENCY_IN_FLIGHT"

	// Slip lookup.
	CodeNoCredit             ErrorCode = "NO_CREDIT"
	CodeCreditExpired        ErrorCode = "CREDIT_EXPIRED"
	CodeCreditUnavailable    ErrorCode = "CREDIT_UNAVAILABLE"
	CodeInvalidQR            ErrorCode = "INVALID_QR"
	CodeQRTampered           ErrorCode = "QR_TAMPERED"
	CodeTooOld               ErrorCode = "TOO_OLD"
	CodeInvalidFile          ErrorCode = "INVALID_FILE"
	CodeMultipleSlipQR       ErrorCode = "MULTIPLE_SLIP_QR"
	CodePayloadTooLarge      ErrorCode = "PAYLOAD_TOO_LARGE"
	CodeUnsupportedMediaType ErrorCode = "UNSUPPORTED_MEDIA_TYPE"
	CodeUnavailable          ErrorCode = "UNAVAILABLE"

	// Billing.
	CodeOrderNotFound       ErrorCode = "ORDER_NOT_FOUND"
	CodeReceiptNotFound     ErrorCode = "RECEIPT_NOT_FOUND"
	CodePaymentsUnavailable ErrorCode = "PAYMENTS_UNAVAILABLE"

	// Webhooks.
	CodeEndpointNotFound         ErrorCode = "ENDPOINT_NOT_FOUND"
	CodeDeliveryNotFound         ErrorCode = "DELIVERY_NOT_FOUND"
	CodeEndpointLimitReached     ErrorCode = "ENDPOINT_LIMIT_REACHED"
	CodeEndpointDisabled         ErrorCode = "ENDPOINT_DISABLED"
	CodeEndpointDeleted          ErrorCode = "ENDPOINT_DELETED"
	CodeRotationInProgress       ErrorCode = "ROTATION_IN_PROGRESS"
	CodePayloadExpired           ErrorCode = "PAYLOAD_EXPIRED"
	CodeURLNotHTTPS              ErrorCode = "URL_NOT_HTTPS"
	CodeURLHasUserinfo           ErrorCode = "URL_HAS_USERINFO"
	CodeDestinationBlocked       ErrorCode = "DESTINATION_BLOCKED"
	CodeDNSUnresolvable          ErrorCode = "DNS_UNRESOLVABLE"
	CodeUnknownEventType         ErrorCode = "UNKNOWN_EVENT_TYPE"
	CodeRecoverWindowInvalid     ErrorCode = "RECOVER_WINDOW_INVALID"
	CodePayloadIntegrityMismatch ErrorCode = "PAYLOAD_INTEGRITY_MISMATCH"
)

Error codes the Checkslip API returns. The list covers the codes a customer is likely to handle; Error.Code can hold others.

type EventType

type EventType string

EventType is the type of a webhook message.

const (
	// EventSlipChecked is sent when a lookup finishes, including a
	// PENDING lookup that later resolves.
	EventSlipChecked EventType = "slip.checked"
	// EventWebhookTest is sent by WebhookEndpointsService.SendTest.
	EventWebhookTest EventType = "webhook.test"
)

Webhook event types.

type EventTypeInfo

type EventTypeInfo struct {
	Type        EventType `json:"type"`
	Description string    `json:"description"`
	// Subscribable reports whether the type can be listed in an
	// endpoint's EventTypes.
	Subscribable bool `json:"subscribable"`
}

EventTypeInfo describes one webhook event type.

type EvidenceExport

type EvidenceExport struct {
	ExportID  string         `json:"export_id"`
	State     ExportState    `json:"status"`
	FromDay   Date           `json:"from_day"`
	ToDay     Date           `json:"to_day"`
	Streams   []Stream       `json:"streams"`
	CreatedAt time.Time      `json:"created_at"`
	CreatedBy *ExportCreator `json:"created_by,omitempty"`
	ReadyAt   *time.Time     `json:"ready_at,omitempty"`
	// ExpiresAt is when the file is deleted. Download it before then.
	ExpiresAt *time.Time      `json:"expires_at,omitempty"`
	Manifest  *ExportManifest `json:"manifest,omitempty"`
}

EvidenceExport is an evidence file of the account.

type EvidenceFile

type EvidenceFile struct {
	io.ReadCloser
	// ReprDigest is the Repr-Digest header, the file's SHA-256.
	ReprDigest string
	// Size is the Content-Length, or -1 when unknown.
	Size int64
}

EvidenceFile is a downloaded evidence file. Read it as NDJSON, one record per line; the last line is the manifest. Close it when done.

type ExportColumn

type ExportColumn string

ExportColumn is one column of a lookup history CSV.

latency_ms, upstream_ms and checked_elsewhere are deliberately absent: they are being withdrawn from the Checkslip API, so they are not offered as export columns.

const (
	ColumnRequestID               ExportColumn = "request_id"
	ColumnOutcome                 ExportColumn = "outcome"
	ColumnStatus                  ExportColumn = "status"
	ColumnCheckedAt               ExportColumn = "checked_at"
	ColumnChannel                 ExportColumn = "channel"
	ColumnAPIKeyID                ExportColumn = "api_key_id"
	ColumnClientReference         ExportColumn = "client_reference"
	ColumnDuplicateOfRequestID    ExportColumn = "duplicate_of.request_id"
	ColumnChargeConfirmed         ExportColumn = "charge_confirmed"
	ColumnCharged                 ExportColumn = "charged"
	ColumnFreeReason              ExportColumn = "free_reason"
	ColumnSlipTransRef            ExportColumn = "slip.trans_ref"
	ColumnSlipTransferredAt       ExportColumn = "slip.transferred_at"
	ColumnSlipAmountSatang        ExportColumn = "slip.amount.satang"
	ColumnSlipAmountCurrency      ExportColumn = "slip.amount.currency"
	ColumnSlipSenderBankCode      ExportColumn = "slip.sender.bank.code"
	ColumnSlipSenderBankBOTCode   ExportColumn = "slip.sender.bank.bot_code"
	ColumnSlipSenderNameTH        ExportColumn = "slip.sender.name_th"
	ColumnSlipSenderNameEN        ExportColumn = "slip.sender.name_en"
	ColumnSlipSenderAccount       ExportColumn = "slip.sender.account"
	ColumnSlipReceiverBankCode    ExportColumn = "slip.receiver.bank.code"
	ColumnSlipReceiverBankBOTCode ExportColumn = "slip.receiver.bank.bot_code"
	ColumnSlipReceiverNameTH      ExportColumn = "slip.receiver.name_th"
	ColumnSlipReceiverNameEN      ExportColumn = "slip.receiver.name_en"
	ColumnSlipReceiverAccount     ExportColumn = "slip.receiver.account"
)

Export columns.

type ExportCreator

type ExportCreator struct {
	Type ActorType `json:"type"`
	ID   *string   `json:"id,omitempty"`
}

ExportCreator is who asked for an evidence file.

type ExportManifest

type ExportManifest struct {
	// Format is "checkslip-audit-export/3".
	Format string `json:"format"`
	// RecordCount is how many records the file holds.
	RecordCount int64 `json:"record_count"`
	// SHA256 covers every byte of the file before its last line.
	SHA256    string `json:"sha256"`
	SizeBytes int64  `json:"size_bytes"`
}

ExportManifest describes a READY evidence file.

type ExportMode

type ExportMode string

ExportMode says whether a lookup history export came back at once or runs as a background job.

const (
	ExportDirect ExportMode = "DIRECT"
	ExportJob    ExportMode = "JOB"
)

Export modes.

type ExportPart

type ExportPart struct {
	Part int    `json:"part"`
	Rows *int64 `json:"rows,omitempty"`
	// URL is a signed download link. Pass the part to DownloadExportPart.
	URL          string    `json:"url"`
	URLExpiresAt time.Time `json:"url_expires_at"`
}

ExportPart is one downloadable CSV file of an export.

type ExportState

type ExportState string

ExportState is the state of a lookup history export or an evidence file.

const (
	ExportQueued  ExportState = "QUEUED"
	ExportRunning ExportState = "RUNNING"
	ExportReady   ExportState = "READY"
	ExportFailed  ExportState = "FAILED"
	// ExportExpired means the file was deleted.
	ExportExpired ExportState = "EXPIRED"
)

Export states.

type FieldError

type FieldError struct {
	// Pointer is a JSON Pointer to the field, for example "/qr".
	Pointer string `json:"pointer"`
	// Code says what is wrong with the field.
	Code string `json:"code"`
	// Detail explains it for people, when sent.
	Detail string `json:"detail,omitempty"`
}

FieldError points at one request field that failed validation.

type Fingerprint

type Fingerprint struct {
	QRSHA256 string `json:"qr_sha256,omitempty"`
	TransRef string `json:"trans_ref,omitempty"`
}

Fingerprint identifies the slip a request carried without storing it.

type FreeReason

type FreeReason string

FreeReason says why a lookup used no credit.

const (
	FreeReasonNotFound    FreeReason = "NOT_FOUND"
	FreeReasonDuplicate   FreeReason = "DUPLICATE"
	FreeReasonPending     FreeReason = "PENDING"
	FreeReasonUnavailable FreeReason = "UNAVAILABLE"
	FreeReasonInvalidQR   FreeReason = "INVALID_QR"
	FreeReasonInvalidFile FreeReason = "INVALID_FILE"
	FreeReasonQRTampered  FreeReason = "QR_TAMPERED"
	FreeReasonTooOld      FreeReason = "TOO_OLD"
	FreeReasonNoCredit    FreeReason = "NO_CREDIT"
	FreeReasonHoldExpired FreeReason = "HOLD_EXPIRED"
	FreeReasonSystemError FreeReason = "SYSTEM_ERROR"
)

Free reasons.

type Iter

type Iter[T any] struct {
	// contains filtered or unexported fields
}

Iter walks every item of a list across pages, fetching the next page with next_cursor as it goes. Get one from a ListAll method:

it := client.Slips.ListAll(ctx, &checkslip.ListSlipsParams{})
for it.Next() {
	rec := it.Current()
	_ = rec
}
if err := it.Err(); err != nil {
	return err
}

With Go 1.23 or later you can also range over Iter.All.

An Iter is not safe for concurrent use.

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	checkslip "github.com/Checkslip/sdk-go"
)

// fakeAPI stands in for api.checkslip.com so the examples run offline.
func fakeAPI() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/slips/lookup":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"request_id":"0192d4e0-7c1a-7b3e-9f00-1a2b3c4d5e6f","outcome":"FOUND","checked_at":"2026-10-29T10:41:02+07:00",
"slip":{"trans_ref":"630210534087","transferred_at":"2026-10-29T10:33:30+07:00","amount":{"satang":5000,"currency":"THB"},
"sender":{"bank":{"code":"KKP","bot_code":"069","name_th":null,"name_en":"Kiatnakin Phatra Bank"},"name_th":null,"name_en":"MR. SOMCHAI J","account":"0439"},
"receiver":{"kind":"BANK_ACCOUNT","bank":{"code":"GSB","bot_code":"030","name_th":null,"name_en":"Government Savings Bank"},"name_th":null,"name_en":"MISS SOMYING R","account":"9681"}}}`)
		case "/api/billing/balance":
			w.Header().Set("Content-Type", "application/problem+json")
			w.Header().Set("X-Request-Id", "0192f3a4-7b1c-7d2e-8f30-4a5b6c7d8e9f")
			w.WriteHeader(http.StatusForbidden)
			fmt.Fprint(w, `{"type":"https://checkslip.com/problems/forbidden","title":"Forbidden","status":403,"code":"FORBIDDEN","instance":"/api/billing/balance"}`)
		case "/api/slips":
			w.Header().Set("Content-Type", "application/json")
			if r.URL.Query().Get("cursor") == "" {
				fmt.Fprint(w, `{"items":[{"request_id":"r1","outcome":"FOUND","checked_at":"2026-11-01T09:00:00Z","channel":"API"}],"next_cursor":"p2"}`)
				return
			}
			fmt.Fprint(w, `{"items":[{"request_id":"r2","outcome":"NOT_FOUND","checked_at":"2026-11-01T08:00:00Z","channel":"API"}],"next_cursor":null}`)
		default:
			http.NotFound(w, r)
		}
	}))
}

func main() {
	srv := fakeAPI()
	defer srv.Close()
	client := checkslip.NewClient("csk_example", checkslip.WithBaseURL(srv.URL))

	it := client.Slips.ListAll(context.Background(), &checkslip.ListSlipsParams{
		ListOptions: checkslip.ListOptions{Limit: 100},
	})
	for it.Next() {
		rec := it.Current()
		fmt.Println(rec.RequestID, rec.Outcome)
	}
	if err := it.Err(); err != nil {
		fmt.Println(err)
	}
}
Output:
r1 FOUND
r2 NOT_FOUND

func (*Iter[T]) All

func (it *Iter[T]) All() iter.Seq2[T, error]

All returns the remaining items as an iterator for range loops. The loop gets a non-nil error at most once, as its last value; stop on it.

for rec, err := range client.Slips.ListAll(ctx, nil).All() {
	if err != nil {
		return err
	}
	fmt.Println(rec.RequestID)
}

All needs Go 1.23 or later. On Go 1.22 use Next, Current and Err.

Example
srv := fakeAPI()
defer srv.Close()
client := checkslip.NewClient("csk_example", checkslip.WithBaseURL(srv.URL))

for rec, err := range client.Slips.ListAll(context.Background(), nil).All() {
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(rec.RequestID)
}
Output:
r1
r2

func (*Iter[T]) Current

func (it *Iter[T]) Current() T

Current returns the item Next moved to.

func (*Iter[T]) Cursor

func (it *Iter[T]) Cursor() string

Cursor returns the cursor of the page after the one being read. Save it to resume a long walk later with ListOptions.Cursor. It is empty once the last page has been fetched.

func (*Iter[T]) Err

func (it *Iter[T]) Err() error

Err returns the error that stopped the iteration, or nil when the list ended normally.

func (*Iter[T]) Next

func (it *Iter[T]) Next() bool

Next moves to the next item and reports whether there is one. It fetches a new page when the current one is used up. When Next returns false, check Err.

type LedgerParams

type LedgerParams struct {
	ListOptions
	// Reason keeps only these reasons.
	Reason []CreditReason
	// From and To limit the time range. Zero means no limit.
	From, To time.Time
}

LedgerParams filters the credit ledger.

type ListAuditEventsParams

type ListAuditEventsParams struct {
	ListOptions
	From, To time.Time
	// Action is a full action name, or a group ending in ".*" such as
	// "api_key.*".
	Action     string
	ActorType  ActorType
	ActorID    string
	Channel    Channel
	TargetType string
	TargetID   string
}

ListAuditEventsParams filters the account activity. Without From and To the API returns the last 24 hours; one call covers at most 31 days.

type ListDeliveriesParams

type ListDeliveriesParams struct {
	ListOptions
	EndpointID string
	Status     DeliveryStatus
	EventType  EventType
	// Since and Until limit the time range. Zero means no limit.
	Since, Until time.Time
}

ListDeliveriesParams filters the delivery list.

type ListOptions

type ListOptions struct {
	// Limit is the page size, 1 to 100. Zero uses the API default of 20.
	Limit int
	// Cursor is the NextCursor of the previous page. Leave it empty for the
	// first page.
	Cursor string
}

ListOptions holds the paging fields every list call accepts.

type ListRequestsParams

type ListRequestsParams struct {
	ListOptions
	From, To    time.Time
	HTTPStatus  int
	OutcomeCode string
	Route       string
	Channel     Channel
	APIKeyID    string
	// TransRef or QRSHA256 find whether a slip was sent before.
	TransRef string
	QRSHA256 string
}

ListRequestsParams filters the request records. Without From and To the API returns the last 24 hours; one call covers at most 31 days.

type ListSlipsParams

type ListSlipsParams struct {
	ListOptions
	// Q is an exact match (never partial) on a request ID, transfer
	// reference or client reference. It ignores From and To.
	Q string
	// TransRef finds lookups of one transfer reference, for example to see
	// whether a slip was sent before.
	TransRef string
	// Outcome keeps only these outcomes.
	Outcome []Outcome
	// OutcomeNot drops these outcomes.
	OutcomeNot []Outcome
	// SenderBank is the three-digit Bank of Thailand code of the sender.
	SenderBank string
	// APIKey is the 12-character ID of the API key that made the lookup.
	APIKey string
	// After returns only lookups newer than this request ID. Do not
	// combine it with a cursor.
	After string
	// Charged filters by whether a credit was used.
	Charged []ChargedFilter
	// FreeReason keeps lookups that used no credit for these reasons.
	FreeReason []FreeReason
	// From and To limit the time range. Zero means no limit.
	From, To time.Time
}

ListSlipsParams filters the lookup history. Every field is optional.

type LookupParams

type LookupParams struct {
	// QR is the text of the QR code printed on the slip.
	QR string `json:"qr,omitempty"`
	// Image is the slip picture instead of its QR text: JPEG, PNG or WebP,
	// at most 10 MB and 50 megapixels. The API reads the QR from the image
	// and does not store the image. The client reads it fully into memory so
	// the request can be sent again on a retry.
	Image io.Reader `json:"-"`
	// Filename is sent as the file name of an image upload. Optional.
	Filename string `json:"-"`
	// ContentType is the image type, such as "image/png". When empty the
	// client detects it from the first bytes.
	ContentType string `json:"-"`
	// ClientReference is your own reference for this lookup, such as an
	// order number. It comes back in the answer, the history and webhooks.
	ClientReference string `json:"client_reference,omitempty"`
}

LookupParams is the input of SlipsService.Lookup.

Set exactly one of QR or Image. Setting both, or neither, is an error the client reports before it sends anything.

type Money

type Money struct {
	// Satang is the amount in satang (1/100 baht).
	Satang int64 `json:"satang"`
	// Currency is the ISO 4217 code, "THB" for every slip today.
	Currency string `json:"currency"`
}

Money is an amount in the smallest unit of its currency. For THB that is satang: 5000 satang is 50.00 baht.

func (Money) String

func (m Money) String() string

String formats the amount with two decimals and the currency, for example "1250.50 THB".

type Option

type Option func(*Client)

Option configures a Client. Pass options to NewClient.

func WithBaseURL

func WithBaseURL(rawURL string) Option

WithBaseURL sets the API address, for example a local mock server in tests. The default is DefaultBaseURL. Plain http is accepted only for loopback hosts such as 127.0.0.1 and localhost, so an API key is never sent in clear text over a network.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets the http.Client used to send requests. Use it to add a proxy, custom TLS settings or instrumentation. The client's own Timeout, if any, applies on top of the per-attempt timeout from WithTimeout.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets how many times a failed request is sent again. The default is 2, so a request is sent at most 3 times. Zero turns retries off. See the package documentation for which failures are retried.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets how long a single attempt may take, including reading the response body. Retries get a fresh timeout each. The default is 30 seconds. For downloads (DownloadExport, DownloadExportPart) it covers the wait for the response headers only; the context limits reading the file. Zero or a negative value turns the per-attempt timeout off; the context passed to each method still applies.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent puts your application's name and version in front of the SDK's own User-Agent, for example "my-shop/1.4". It helps Checkslip support find your requests when you ask about them.

type Order

type Order struct {
	ID            string        `json:"id"`
	Status        OrderStatus   `json:"status"`
	PackageCode   string        `json:"package_code"`
	PackageNameTH string        `json:"package_name_th"`
	Credits       int64         `json:"credits"`
	Amount        Money         `json:"amount"`
	PaymentMethod PaymentMethod `json:"payment_method,omitempty"`
	ReceiptID     *string       `json:"receipt_id,omitempty"`
	CreatedAt     time.Time     `json:"created_at"`
	PaidAt        *time.Time    `json:"paid_at,omitempty"`
	CancelledAt   *time.Time    `json:"cancelled_at,omitempty"`
	// Confirming is true when the payment page was closed but the money
	// has not arrived yet.
	Confirming   bool    `json:"confirming,omitempty"`
	ReviewReason *string `json:"review_reason,omitempty"`
}

Order is a purchase of credits.

type OrderStatus

type OrderStatus string

OrderStatus is the state of a credit order.

const (
	OrderPending   OrderStatus = "PENDING"
	OrderPaid      OrderStatus = "PAID"
	OrderCancelled OrderStatus = "CANCELLED"
	OrderFlagged   OrderStatus = "FLAGGED"
)

Order statuses.

type OrdersParams

type OrdersParams struct {
	ListOptions
	Status OrderStatus
}

OrdersParams filters the order list.

type Outcome

type Outcome string

Outcome is the result of one slip lookup.

const (
	// OutcomeFound means the sending bank returned the transfer. This is
	// the only outcome that uses a credit.
	OutcomeFound Outcome = "FOUND"
	// OutcomeNotFound means the bank has no transfer for this reference.
	// Whether to accept the payment is your decision.
	OutcomeNotFound Outcome = "NOT_FOUND"
	// OutcomeDuplicate means this account looked up the same transfer
	// before; DuplicateOf points at the first lookup.
	OutcomeDuplicate Outcome = "DUPLICATE"
	// OutcomePending means the bank does not show the transfer yet. Read
	// the result later with Slips.Get or wait for the slip.checked webhook.
	OutcomePending Outcome = "PENDING"
	// OutcomeUnavailable means the bank could not be reached.
	OutcomeUnavailable Outcome = "UNAVAILABLE"
	// OutcomeInvalidQR means the QR text could not be read as a slip QR.
	OutcomeInvalidQR Outcome = "INVALID_QR"
	// OutcomeInvalidFile means the image could not be read.
	OutcomeInvalidFile Outcome = "INVALID_FILE"
	// OutcomeQRTampered means the QR text failed its integrity check.
	OutcomeQRTampered Outcome = "QR_TAMPERED"
	// OutcomeTooOld means the transfer is older than the bank lets us look
	// back (see SupportedBank.LookbackDays).
	OutcomeTooOld Outcome = "TOO_OLD"
)

Outcomes. Lookup returns FOUND, NOT_FOUND, DUPLICATE or PENDING; the other values appear in the lookup history and in webhooks for lookups the API answered with an error.

type Package

type Package struct {
	Code           string `json:"code"`
	NameTH         string `json:"name_th"`
	NameEN         string `json:"name_en"`
	Price          Money  `json:"price"`
	PricePerCredit Money  `json:"price_per_credit"`
	Credits        int64  `json:"credits"`
	// ValidityDays is how long the credits last. Credits from every
	// package join one balance that lasts from the latest purchase.
	ValidityDays int  `json:"validity_days"`
	ForSale      bool `json:"for_sale"`
	Sort         int  `json:"sort"`
}

Package is a credit package for sale.

type PackageList

type PackageList struct {
	// CheckoutOpen reports whether packages can be bought right now.
	CheckoutOpen     bool      `json:"checkout_open"`
	PricesIncludeVAT bool      `json:"prices_include_vat"`
	Items            []Package `json:"items"`
}

PackageList is the list of credit packages.

type Page

type Page[T any] struct {
	Items      []T    `json:"items"`
	NextCursor string `json:"next_cursor"`
}

Page is one page of a list. NextCursor is empty on the last page.

func (*Page[T]) HasMore

func (p *Page[T]) HasMore() bool

HasMore reports whether another page follows.

func (*Page[T]) UnmarshalJSON

func (p *Page[T]) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler.

type Party

type Party struct {
	Bank Bank `json:"bank"`
	// NameTH and NameEN are the names the bank holds, often shortened by
	// the bank. Either may be nil.
	NameTH *string `json:"name_th"`
	NameEN *string `json:"name_en"`
	// Account is the part of the account number the bank shows, or nil.
	Account *string `json:"account"`
}

Party is the sender of a transfer as the bank returned it.

type PaymentMethod

type PaymentMethod string

PaymentMethod is how an order was paid.

const (
	PaymentCard      PaymentMethod = "CARD"
	PaymentPromptPay PaymentMethod = "PROMPTPAY"
)

Payment methods.

type Receipt

type Receipt struct {
	ID string `json:"id"`
	// SellerReady reports whether the seller details are complete.
	SellerReady   bool          `json:"seller_ready"`
	ReceiptNo     string        `json:"receipt_no"`
	OrderID       string        `json:"order_id"`
	IssuedAt      time.Time     `json:"issued_at"`
	Seller        ReceiptSeller `json:"seller"`
	Buyer         BillingInfo   `json:"buyer"`
	Lines         []ReceiptLine `json:"lines"`
	VAT           *ReceiptVAT   `json:"vat,omitempty"`
	Total         Money         `json:"total"`
	PaymentMethod PaymentMethod `json:"payment_method"`
}

Receipt is a tax receipt for a paid order.

type ReceiptLine

type ReceiptLine struct {
	Description string `json:"description"`
	Quantity    int    `json:"quantity"`
	Amount      Money  `json:"amount"`
}

ReceiptLine is one line of a receipt.

type ReceiptSeller

type ReceiptSeller struct {
	Name    string `json:"name"`
	TaxID   string `json:"tax_id,omitempty"`
	Address string `json:"address,omitempty"`
	Branch  string `json:"branch,omitempty"`
}

ReceiptSeller is the seller printed on a receipt.

type ReceiptVAT

type ReceiptVAT struct {
	// RateBP is the rate in basis points: 700 is 7%.
	RateBP int   `json:"rate_bp"`
	Base   Money `json:"base"`
	Amount Money `json:"amount"`
}

ReceiptVAT is the VAT on a receipt.

type Receiver

type Receiver struct {
	Kind ReceiverKind `json:"kind"`
	// Bank is nil for some bill payments.
	Bank    *Bank   `json:"bank"`
	NameTH  *string `json:"name_th"`
	NameEN  *string `json:"name_en"`
	Account *string `json:"account"`
}

Receiver is the receiving side of a transfer.

type ReceiverKind

type ReceiverKind string

ReceiverKind says what kind of account received a transfer.

const (
	ReceiverBankAccount    ReceiverKind = "BANK_ACCOUNT"
	ReceiverBillPayment    ReceiverKind = "BILL_PAYMENT"
	ReceiverNotBankAccount ReceiverKind = "NOT_BANK_ACCOUNT"
)

Receiver kinds.

type RecoverParams

type RecoverParams struct {
	// Since is the start of the range. Required.
	Since time.Time `json:"since"`
	// Until is the end, not included. Zero means now.
	Until time.Time `json:"-"`
}

RecoverParams is the time range of WebhookEndpointsService.Recover.

func (RecoverParams) MarshalJSON

func (p RecoverParams) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

type RecoverResult

type RecoverResult struct {
	EndpointID string    `json:"endpoint_id"`
	Since      time.Time `json:"since"`
	Until      time.Time `json:"until"`
}

RecoverResult confirms a recover request.

type RequestOption

type RequestOption func(*requestConfig)

RequestOption changes a single API call. Pass it as the last argument of any service method.

func WithHeader

func WithHeader(key, value string) RequestOption

WithHeader adds an HTTP header to this call, for example a W3C traceparent header. It cannot change Authorization, Content-Type or Idempotency-Key.

func WithIdempotencyKey

func WithIdempotencyKey(key string) RequestOption

WithIdempotencyKey sends the Idempotency-Key header with this call. A call sent again with the same key returns the first result and is not processed twice; a lookup is never charged twice.

Methods that need a key generate a random one when you do not pass one. Pass your own, for example derived from your order ID, when you may resend the same request after a restart of your program.

Example
package main

import (
	"context"
	"fmt"
	"os"

	checkslip "github.com/Checkslip/sdk-go"
)

func main() {
	client := checkslip.NewClient(os.Getenv("CHECKSLIP_API_KEY"))
	// A key tied to your order lets you resend after a crash and get the
	// first answer back, with no second charge.
	res, err := client.Slips.Lookup(context.Background(),
		&checkslip.LookupParams{QR: "..."},
		checkslip.WithIdempotencyKey("order-1001-slip-1"),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(res.Outcome)
}

type SecretRotation

type SecretRotation struct {
	// Secret is the new signing secret, returned only once.
	Secret string `json:"secret"`
	// PreviousSecretExpiresAt is when the old secret stops signing. Until
	// then every message carries both signatures.
	PreviousSecretExpiresAt time.Time `json:"previous_secret_expires_at"`
}

SecretRotation is the result of WebhookEndpointsService.RotateSecret.

type Slip

type Slip struct {
	// TransRef is the transfer reference printed on the slip.
	TransRef string `json:"trans_ref"`
	// TransferredAt is when the bank says the money moved. It is the zero
	// time on summaries that do not carry it.
	TransferredAt time.Time `json:"transferred_at"`
	// Amount is the transferred amount. It is nil on summaries that do not
	// carry it.
	Amount *Money `json:"amount,omitempty"`

	// Sender and Receiver are set on a full slip.
	Sender   *Party    `json:"sender,omitempty"`
	Receiver *Receiver `json:"receiver,omitempty"`
	// NamesErased is true when the names and masked accounts of this
	// transfer were erased together with the account's data. The name and
	// account fields are then nil because Checkslip no longer has them. A
	// nil name without NamesErased means the bank did not report it.
	NamesErased bool `json:"names_erased,omitempty"`

	// SenderBank and ReceiverBank are set on a summary.
	SenderBank   *Bank `json:"sender_bank,omitempty"`
	ReceiverBank *Bank `json:"receiver_bank,omitempty"`
}

Slip is the transfer data returned with a lookup.

A FOUND lookup carries the full slip: Sender and Receiver are set. Other outcomes and some history rows carry a summary: SenderBank (and maybe ReceiverBank) are set and Sender and Receiver are nil.

func (*Slip) IsSummary

func (s *Slip) IsSummary() bool

IsSummary reports whether s is a summary without sender and receiver details.

type SlipCheck

type SlipCheck struct {
	// RequestID identifies this lookup. Read it again with Slips.Get.
	RequestID string `json:"request_id"`
	// Outcome is FOUND, NOT_FOUND, DUPLICATE or PENDING.
	Outcome   Outcome   `json:"outcome"`
	CheckedAt time.Time `json:"checked_at"`
	// latency_ms, upstream_ms and checked_elsewhere are being withdrawn from
	// the Checkslip API. The SDK does not carry them, so your code will not
	// break when the API stops sending them.
	// Slip is the transfer data. It may be a summary; see Slip.
	Slip *Slip `json:"slip"`
	// DuplicateOf is set when Outcome is DUPLICATE.
	DuplicateOf *DuplicateOf `json:"duplicate_of,omitempty"`
	// ClientReference echoes the reference you sent, if any.
	ClientReference *string `json:"client_reference,omitempty"`

	// IdempotencyKey is the Idempotency-Key the request carried. Send the
	// same lookup again with this key to get this same answer.
	IdempotencyKey string `json:"-"`
	// RetryAfter is set on a PENDING answer: the API's suggested wait
	// before reading the result again with Slips.Get.
	RetryAfter time.Duration `json:"-"`
}

SlipCheck is the answer to Slips.Lookup.

The API returns what the sending bank holds. Whether the payment is acceptable for your order is your decision: compare at least Amount, Receiver and TransferredAt with what you expected.

type SlipCheckRecord

type SlipCheckRecord struct {
	RequestID string    `json:"request_id"`
	Outcome   Outcome   `json:"outcome"`
	CheckedAt time.Time `json:"checked_at"`
	// APIKeyID is the key that made the lookup, or nil for lookups made in
	// the web app.
	APIKeyID *string `json:"api_key_id,omitempty"`
	// Status is the HTTP status the lookup was answered with.
	Status int `json:"status,omitempty"`
	// latency_ms, upstream_ms and checked_elsewhere are being withdrawn from
	// the Checkslip API. The SDK does not carry them, so your code will not
	// break when the API stops sending them.
	Channel         Channel      `json:"channel"`
	Slip            *Slip        `json:"slip,omitempty"`
	DuplicateOf     *DuplicateOf `json:"duplicate_of,omitempty"`
	ClientReference *string      `json:"client_reference,omitempty"`
	// Charged is true when the lookup used a credit, false when it did
	// not, and nil while that is not settled yet.
	Charged *bool `json:"charged,omitempty"`
	// ChargeConfirmed is nil until the charge is settled.
	ChargeConfirmed *bool `json:"charge_confirmed,omitempty"`
	// FreeReason says why no credit was used, when Charged is false.
	FreeReason *FreeReason `json:"free_reason,omitempty"`
}

SlipCheckRecord is one row of the lookup history.

type SlipCheckedData

type SlipCheckedData struct {
	RequestID string    `json:"request_id"`
	Outcome   Outcome   `json:"outcome"`
	CheckedAt time.Time `json:"checked_at"`
	// Channel is API, DASHBOARD or ASSISTANT.
	Channel         Channel      `json:"channel"`
	ClientReference *string      `json:"client_reference"`
	Slip            *Slip        `json:"slip,omitempty"`
	DuplicateOf     *DuplicateOf `json:"duplicate_of,omitempty"`
	// ResolvedAfterPending is true when this lookup was PENDING first.
	ResolvedAfterPending bool `json:"resolved_after_pending"`
}

SlipCheckedData is the data of a slip.checked message.

type SlipCounts

type SlipCounts struct {
	Bucket CountBucket `json:"bucket"`
	// AsOf is the time the counts were computed.
	AsOf time.Time `json:"as_of"`
	// FiltersApplied lists the filters the counts honor.
	FiltersApplied []string `json:"filters_applied"`
	// FiltersIgnored lists filters that were sent but do not apply to
	// counts.
	FiltersIgnored []string      `json:"filters_ignored"`
	Series         []CountSeries `json:"series"`
}

SlipCounts is the number of lookups per time bucket.

type SlipCountsParams

type SlipCountsParams struct {
	// From and To are the time range. Required.
	From, To time.Time
	// Bucket is the size of each count. Required.
	Bucket CountBucket
	// Outcome and OutcomeNot filter by outcome.
	Outcome    []Outcome
	OutcomeNot []Outcome
}

SlipCountsParams is the input of SlipsService.Counts.

type SlipExport

type SlipExport struct {
	ExportID  string           `json:"export_id"`
	State     ExportState      `json:"state"`
	Mode      ExportMode       `json:"mode,omitempty"`
	CreatedAt time.Time        `json:"created_at"`
	Request   SlipExportParams `json:"request"`
	// Rows is the number of rows, once known.
	Rows *int64 `json:"rows,omitempty"`
	// Truncated is true when the export hit its row limit. Create another
	// export with Cursor set to ContinueCursor for the rest.
	Truncated      bool    `json:"truncated,omitempty"`
	ContinueCursor *string `json:"continue_cursor,omitempty"`
	// ExpiresAt is when the parts are deleted.
	ExpiresAt *time.Time   `json:"expires_at,omitempty"`
	Parts     []ExportPart `json:"parts,omitempty"`
}

SlipExport is a CSV export of the lookup history.

type SlipExportParams

type SlipExportParams struct {
	// Columns lists the CSV columns in order. Required.
	Columns []ExportColumn `json:"columns"`
	// Q, TransRef and the other filters work as in ListSlipsParams.
	Q          string          `json:"q,omitempty"`
	TransRef   string          `json:"trans_ref,omitempty"`
	Outcome    []Outcome       `json:"outcome,omitempty"`
	OutcomeNot []Outcome       `json:"outcome_not,omitempty"`
	SenderBank string          `json:"sender_bank,omitempty"`
	APIKey     string          `json:"api_key,omitempty"`
	Charged    []ChargedFilter `json:"charged,omitempty"`
	FreeReason []FreeReason    `json:"free_reason,omitempty"`
	From       *time.Time      `json:"from,omitempty"`
	To         *time.Time      `json:"to,omitempty"`
	// Cursor continues a truncated export: pass SlipExport.ContinueCursor.
	Cursor string `json:"cursor,omitempty"`
}

SlipExportParams describes a CSV export of the lookup history.

type SlipExportsService

type SlipExportsService struct {
	// contains filtered or unexported fields
}

SlipExportsService builds and downloads CSV files of past lookups.

func (*SlipExportsService) Create

func (s *SlipExportsService) Create(ctx context.Context, params *SlipExportParams, opts ...RequestOption) (*SlipExport, error)

Create starts a CSV export of the lookup history. Up to 1,000,000 rows come back at once (Mode DIRECT); more runs as a background job (Mode JOB): poll GetExport until State is READY. One export runs per account at a time. The file holds payer and payee names, so store it with care.

func (*SlipExportsService) DownloadPart

func (s *SlipExportsService) DownloadPart(ctx context.Context, part ExportPart, opts ...RequestOption) (io.ReadCloser, error)

DownloadPart downloads one CSV part of a READY export. The caller must close the returned body. The part's URL must point at the API host the client uses; the client refuses other hosts so the API key stays with Checkslip.

func (*SlipExportsService) Get

func (s *SlipExportsService) Get(ctx context.Context, exportID string, opts ...RequestOption) (*SlipExport, error)

Get returns the state of a lookup history export.

type SlipsService

type SlipsService struct {

	// Exports builds CSV files of past lookups.
	Exports *SlipExportsService
	// contains filtered or unexported fields
}

SlipsService looks up slips and reads the lookup history.

func (*SlipsService) Counts

func (s *SlipsService) Counts(ctx context.Context, params *SlipCountsParams, opts ...RequestOption) (*SlipCounts, error)

Counts returns how many lookups were made per time bucket, split by outcome.

func (*SlipsService) Get

func (s *SlipsService) Get(ctx context.Context, requestID string, opts ...RequestOption) (*SlipCheckRecord, error)

Get returns one lookup of this account by its request ID. Use it to read the result of a PENDING lookup later. An unknown ID and another account's ID both return an error for which IsNotFound is true.

func (*SlipsService) List

func (s *SlipsService) List(ctx context.Context, params *ListSlipsParams, opts ...RequestOption) (*Page[SlipCheckRecord], error)

List returns one page of the lookup history, newest first. FOUND rows carry the payer and payee names with masked account numbers.

func (*SlipsService) ListAll

func (s *SlipsService) ListAll(ctx context.Context, params *ListSlipsParams, opts ...RequestOption) *Iter[SlipCheckRecord]

ListAll walks the whole lookup history that matches params, page by page.

func (*SlipsService) Lookup

func (s *SlipsService) Lookup(ctx context.Context, params *LookupParams, opts ...RequestOption) (*SlipCheck, error)

Lookup asks the sending bank for the transfer a slip refers to. Pass either the QR text of the slip in QR, or the slip image in Image.

The answer's Outcome is FOUND, NOT_FOUND, DUPLICATE or PENDING. Only FOUND uses a credit. A QR the API cannot use comes back as an *Error with a code such as CodeInvalidQR, CodeQRTampered or CodeTooOld, and uses no credit.

Lookup always sends an Idempotency-Key, generating one unless you pass WithIdempotencyKey, so retries never charge twice. The key used is in SlipCheck.IdempotencyKey.

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	checkslip "github.com/Checkslip/sdk-go"
)

// fakeAPI stands in for api.checkslip.com so the examples run offline.
func fakeAPI() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/slips/lookup":
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"request_id":"0192d4e0-7c1a-7b3e-9f00-1a2b3c4d5e6f","outcome":"FOUND","checked_at":"2026-10-29T10:41:02+07:00",
"slip":{"trans_ref":"630210534087","transferred_at":"2026-10-29T10:33:30+07:00","amount":{"satang":5000,"currency":"THB"},
"sender":{"bank":{"code":"KKP","bot_code":"069","name_th":null,"name_en":"Kiatnakin Phatra Bank"},"name_th":null,"name_en":"MR. SOMCHAI J","account":"0439"},
"receiver":{"kind":"BANK_ACCOUNT","bank":{"code":"GSB","bot_code":"030","name_th":null,"name_en":"Government Savings Bank"},"name_th":null,"name_en":"MISS SOMYING R","account":"9681"}}}`)
		case "/api/billing/balance":
			w.Header().Set("Content-Type", "application/problem+json")
			w.Header().Set("X-Request-Id", "0192f3a4-7b1c-7d2e-8f30-4a5b6c7d8e9f")
			w.WriteHeader(http.StatusForbidden)
			fmt.Fprint(w, `{"type":"https://checkslip.com/problems/forbidden","title":"Forbidden","status":403,"code":"FORBIDDEN","instance":"/api/billing/balance"}`)
		case "/api/slips":
			w.Header().Set("Content-Type", "application/json")
			if r.URL.Query().Get("cursor") == "" {
				fmt.Fprint(w, `{"items":[{"request_id":"r1","outcome":"FOUND","checked_at":"2026-11-01T09:00:00Z","channel":"API"}],"next_cursor":"p2"}`)
				return
			}
			fmt.Fprint(w, `{"items":[{"request_id":"r2","outcome":"NOT_FOUND","checked_at":"2026-11-01T08:00:00Z","channel":"API"}],"next_cursor":null}`)
		default:
			http.NotFound(w, r)
		}
	}))
}

func main() {
	srv := fakeAPI()
	defer srv.Close()
	client := checkslip.NewClient("csk_example", checkslip.WithBaseURL(srv.URL))

	res, err := client.Slips.Lookup(context.Background(), &checkslip.LookupParams{
		QR:              "00330006000001010306902126302105340875102TH9104F86F",
		ClientReference: "order-1001",
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	switch res.Outcome {
	case checkslip.OutcomeFound:
		// The bank returned the transfer. Compare it with your order
		// before you accept the payment.
		fmt.Println(res.Outcome, res.Slip.TransRef, res.Slip.Amount, *res.Slip.Receiver.Account)
	case checkslip.OutcomePending:
		fmt.Println("bank has not shown it yet; read it again after", res.RetryAfter)
	default:
		fmt.Println(res.Outcome)
	}
}
Output:
FOUND 630210534087 50.00 THB 9681
Example (Image)
package main

import (
	"context"
	"fmt"
	"os"

	checkslip "github.com/Checkslip/sdk-go"
)

func main() {
	client := checkslip.NewClient(os.Getenv("CHECKSLIP_API_KEY"))
	f, err := os.Open("slip.jpg")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer f.Close()

	res, err := client.Slips.Lookup(context.Background(), &checkslip.LookupParams{Image: f})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(res.Outcome)
}

type Stream

type Stream string

Stream is one of the two record streams kept for audit.

const (
	// StreamAPIRequests holds one record per API request.
	StreamAPIRequests Stream = "API_REQUESTS"
	// StreamAuditEvents holds one record per action on the account.
	StreamAuditEvents Stream = "AUDIT_EVENTS"
)

Streams.

type SupportedBank

type SupportedBank struct {
	Code    string `json:"code"`
	BOTCode string `json:"bot_code"`
	NameTH  string `json:"name_th"`
	NameEN  string `json:"name_en"`
	// SupportedSince is the day lookups for this bank started.
	SupportedSince Date `json:"supported_since"`
	// LandingPage reports whether checkslip.com has a page for this bank.
	LandingPage bool `json:"landing_page"`
	// LookbackDays is how many days back a transfer can be looked up.
	// Older slips get CodeTooOld.
	LookbackDays int `json:"lookback_days"`
}

SupportedBank is a bank whose transfers can be looked up.

type ThresholdBasis

type ThresholdBasis string

ThresholdBasis says how Balance.Threshold was computed.

const (
	ThresholdAvg7Days          ThresholdBasis = "AVG_7_DAYS"
	ThresholdFallback10Percent ThresholdBasis = "FALLBACK_10_PERCENT"
	ThresholdNone              ThresholdBasis = "NONE"
)

Threshold bases.

type UpdateEndpointParams

type UpdateEndpointParams struct {
	URL *string `json:"url,omitempty"`
	// Description sets the note. Use ClearDescription to remove it.
	Description *string `json:"-"`
	// ClearDescription removes the note.
	ClearDescription bool        `json:"-"`
	EventTypes       []EventType `json:"event_types,omitempty"`
	// Enabled turns sending off (false) or back on (true). While an
	// endpoint is off, new messages are not stored at all.
	Enabled *bool `json:"enabled,omitempty"`
}

UpdateEndpointParams changes an endpoint. Only the fields you set are sent (JSON Merge Patch).

func (UpdateEndpointParams) MarshalJSON

func (p UpdateEndpointParams) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler. It writes description as null when ClearDescription is set.

type Usage

type Usage struct {
	Interval UsageInterval `json:"interval"`
	// AsOf is when the counts were computed. They trail real time by about
	// one minute.
	AsOf time.Time `json:"as_of"`
	From Date      `json:"from"`
	To   Date      `json:"to"`
	// Timezone is always "Asia/Bangkok".
	Timezone string        `json:"timezone"`
	Buckets  []UsageBucket `json:"buckets"`
	Totals   UsageCounts   `json:"totals"`
}

Usage is a usage report.

type UsageBucket

type UsageBucket struct {
	// Start is the first day of the bucket.
	Start Date `json:"start"`
	UsageCounts
}

UsageBucket is the usage of one day or month.

type UsageCounts

type UsageCounts struct {
	Checks          int64 `json:"checks"`
	Charged         int64 `json:"charged"`
	FreeNotFound    int64 `json:"free_not_found"`
	FreeDuplicate   int64 `json:"free_duplicate"`
	FreeUnavailable int64 `json:"free_unavailable"`
	FreeInvalidQR   int64 `json:"free_invalid_qr"`
	FreeInvalidFile int64 `json:"free_invalid_file"`
	FreeQRTampered  int64 `json:"free_qr_tampered"`
	FreeTooOld      int64 `json:"free_too_old"`
	FreeNoCredit    int64 `json:"free_no_credit"`
	FreeHoldExpired int64 `json:"free_hold_expired"`
	FreeSystemError int64 `json:"free_system_error"`
}

UsageCounts splits lookups by whether they used a credit and, if not, why.

type UsageInterval

type UsageInterval string

UsageInterval is the bucket size of a usage report.

const (
	UsageDay   UsageInterval = "DAY"
	UsageMonth UsageInterval = "MONTH"
)

Usage intervals.

type UsageParams

type UsageParams struct {
	// Interval is DAY (the default) or MONTH.
	Interval UsageInterval
	// From and To are the first and last day, in Thailand time. Required.
	// One request covers at most 92 days or 24 months.
	From, To Date
}

UsageParams is the input of UsageService.Get.

type UsageService

type UsageService struct {
	// contains filtered or unexported fields
}

UsageService reads lookup counts per day or month.

func (*UsageService) Get

func (s *UsageService) Get(ctx context.Context, params *UsageParams, opts ...RequestOption) (*Usage, error)

Get returns how many lookups were made, how many used a credit and how many did not, per day or month in Thailand time. Days or months without lookups are returned as zero. A lookup counts on the day its credit was settled, which can be the next day for a lookup near midnight. It needs an API key with usage:read.

type WebhookDeliveriesService

type WebhookDeliveriesService struct {
	// contains filtered or unexported fields
}

WebhookDeliveriesService reads and resends webhook deliveries.

func (*WebhookDeliveriesService) Get

func (s *WebhookDeliveriesService) Get(ctx context.Context, deliveryID string, opts ...RequestOption) (*DeliveryDetail, error)

Get returns one delivery with its payload and attempts.

func (*WebhookDeliveriesService) List

List returns one page of deliveries, newest first. Deliveries are kept for 30 days.

func (*WebhookDeliveriesService) ListAll

ListAll walks every delivery that matches params.

func (*WebhookDeliveriesService) Redeliver

func (s *WebhookDeliveriesService) Redeliver(ctx context.Context, deliveryID string, opts ...RequestOption) (*Delivery, error)

Redeliver sends the same payload again, byte for byte, with the original webhook-id and a new timestamp and signature. It works on FAILED and SUCCEEDED deliveries. Your system receives the message again, so deduplicate on webhook-id.

type WebhookEndpointsService

type WebhookEndpointsService struct {
	// contains filtered or unexported fields
}

WebhookEndpointsService manages webhook endpoints.

func (*WebhookEndpointsService) Create

Create registers a URL to receive messages. The answer carries the signing secret, which is never shown again. An account can have up to 16 endpoints, and the account owner gets an email for each new one.

Create is not retried after a server error or a network failure, because a retry could register the URL twice.

func (*WebhookEndpointsService) Delete

func (s *WebhookEndpointsService) Delete(ctx context.Context, endpointID string, opts ...RequestOption) error

Delete stops sending to an endpoint at once and cancels its waiting deliveries. A deleted endpoint cannot be restored. To pause sending instead, Update it with Enabled set to false.

func (*WebhookEndpointsService) Get

func (s *WebhookEndpointsService) Get(ctx context.Context, endpointID string, opts ...RequestOption) (*Endpoint, error)

Get returns one endpoint. The full secret is never returned, only SecretHint.

func (*WebhookEndpointsService) List

func (s *WebhookEndpointsService) List(ctx context.Context, params *ListOptions, opts ...RequestOption) (*Page[Endpoint], error)

List returns one page of the account's endpoints.

func (*WebhookEndpointsService) ListAll

func (s *WebhookEndpointsService) ListAll(ctx context.Context, params *ListOptions, opts ...RequestOption) *Iter[Endpoint]

ListAll walks every endpoint.

func (*WebhookEndpointsService) Recover

func (s *WebhookEndpointsService) Recover(ctx context.Context, endpointID string, params *RecoverParams, opts ...RequestOption) (*RecoverResult, error)

Recover resends every FAILED delivery of the endpoint created in the time range, each with its original webhook-id. Use it after your system was down. The range can be at most 14 days and reach back at most 30 days. The work runs in the background; follow it through the endpoint's RETRYING deliveries.

func (*WebhookEndpointsService) RotateSecret

func (s *WebhookEndpointsService) RotateSecret(ctx context.Context, endpointID string, opts ...RequestOption) (*SecretRotation, error)

RotateSecret issues a new signing secret. The old one keeps signing for 24 hours, so you can switch without missing a message; VerifyWebhook accepts both secrets during that time. You cannot rotate again within those 24 hours (CodeRotationInProgress).

func (*WebhookEndpointsService) SendTest

func (s *WebhookEndpointsService) SendTest(ctx context.Context, endpointID string, opts ...RequestOption) (*Delivery, error)

SendTest queues a webhook.test message to the endpoint, signed and retried like a real one. It uses no credit. Follow the returned delivery with Deliveries.Get.

func (*WebhookEndpointsService) Update

func (s *WebhookEndpointsService) Update(ctx context.Context, endpointID string, params *UpdateEndpointParams, opts ...RequestOption) (*Endpoint, error)

Update changes the fields set in params. A new URL is checked again as on Create.

type WebhookEvent

type WebhookEvent struct {
	// ID is the webhook-id header when the event came from VerifyRequest.
	ID   string    `json:"-"`
	Type EventType `json:"type"`
	// Timestamp is when the event happened.
	Timestamp time.Time `json:"timestamp"`
	// Data depends on Type. Decode it with SlipChecked or Test.
	Data json.RawMessage `json:"data"`
}

WebhookEvent is a webhook message body. Check the signature before you trust it.

func ParseWebhookEvent

func ParseWebhookEvent(body []byte) (*WebhookEvent, error)

ParseWebhookEvent decodes a message body. It does not check the signature; use WebhookVerifier.VerifyRequest for that.

func (*WebhookEvent) SlipChecked

func (e *WebhookEvent) SlipChecked() (*SlipCheckedData, error)

SlipChecked decodes Data of a slip.checked event.

func (*WebhookEvent) Test

func (e *WebhookEvent) Test() (*WebhookTestData, error)

Test decodes Data of a webhook.test event.

type WebhookEventTypesService

type WebhookEventTypesService struct {
	// contains filtered or unexported fields
}

WebhookEventTypesService lists the event types Checkslip can send.

func (*WebhookEventTypesService) List

List returns the webhook event types.

The API wraps them in an object with nothing else in it, so the client returns the event types themselves.

type WebhookTestData

type WebhookTestData struct {
	EndpointID string `json:"endpoint_id"`
}

WebhookTestData is the data of a webhook.test message.

type WebhookVerifier

type WebhookVerifier struct {
	// contains filtered or unexported fields
}

WebhookVerifier checks the signature of webhook messages. Create it once with NewWebhookVerifier and reuse it; it is safe for concurrent use.

func NewWebhookVerifier

func NewWebhookVerifier(secrets ...string) (*WebhookVerifier, error)

NewWebhookVerifier returns a verifier for the given endpoint secrets ("whsec_..."). Pass two secrets while you switch to a rotated one; a message is accepted when any of them signed it.

func (*WebhookVerifier) SetTolerance

func (v *WebhookVerifier) SetTolerance(d time.Duration) *WebhookVerifier

SetTolerance changes how far webhook-timestamp may be from now. Zero or a negative value turns the timestamp check off, which you should only do in tests. It returns v so it can be chained.

func (*WebhookVerifier) Verify

func (v *WebhookVerifier) Verify(header http.Header, body []byte) error

Verify checks a message from its headers and raw body. Pass the body exactly as received, before any JSON parsing.

Example
package main

import (
	"fmt"
	"net/http"

	checkslip "github.com/Checkslip/sdk-go"
)

func main() {
	// Values from the Standard Webhooks test vector.
	secret := "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
	body := []byte(`{"test": 2432232314}`)
	h := http.Header{}
	h.Set("webhook-id", "msg_p5jXN8AQM9LWM0D4loKWxJek")
	h.Set("webhook-timestamp", "1614265330")
	h.Set("webhook-signature", "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=")

	verifier, err := checkslip.NewWebhookVerifier(secret)
	if err != nil {
		fmt.Println(err)
		return
	}
	// The vector was signed in 2021. With the default 5-minute tolerance
	// it is refused as a replay; turn the check off only to show the
	// signature itself matches.
	fmt.Println(verifier.Verify(h, body))
	fmt.Println(verifier.SetTolerance(0).Verify(h, body))
}
Output:
checkslip: webhook timestamp outside tolerance
<nil>

func (*WebhookVerifier) VerifyRequest

func (v *WebhookVerifier) VerifyRequest(r *http.Request) (*WebhookEvent, error)

VerifyRequest reads the body of an incoming webhook request (up to MaxWebhookBody bytes), checks its signature and returns the parsed event. The event's ID is the webhook-id header.

Example
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	checkslip "github.com/Checkslip/sdk-go"
)

func main() {
	verifier, err := checkslip.NewWebhookVerifier(os.Getenv("CHECKSLIP_WEBHOOK_SECRET"))
	if err != nil {
		fmt.Println(err)
		return
	}
	http.HandleFunc("/hooks/checkslip", func(w http.ResponseWriter, r *http.Request) {
		ev, err := verifier.VerifyRequest(r)
		if err != nil {
			http.Error(w, "bad signature", http.StatusUnauthorized)
			return
		}
		// ev.ID is the same on every resend: skip it if you have seen it.
		if ev.Type == checkslip.EventSlipChecked {
			data, err := ev.SlipChecked()
			if err != nil {
				http.Error(w, "bad payload", http.StatusBadRequest)
				return
			}
			log.Println(data.RequestID, data.Outcome)
		}
		w.WriteHeader(http.StatusNoContent)
	})
}

type WebhooksService

type WebhooksService struct {

	// Endpoints manages the URLs that receive messages.
	Endpoints *WebhookEndpointsService
	// Deliveries reads and resends sent messages.
	Deliveries *WebhookDeliveriesService
	// EventTypes lists the event types Checkslip can send.
	EventTypes *WebhookEventTypesService
	// contains filtered or unexported fields
}

WebhooksService manages webhook endpoints and their deliveries. To check the signature of a message your server receives, see VerifyWebhook.

func (*WebhooksService) Verify

func (s *WebhooksService) Verify(header http.Header, body []byte, secrets ...string) (*WebhookEvent, error)

Verify checks the signature of a message your server received and returns the parsed event. It sends nothing, so it works on a client whose API key is never used. During a secret rotation pass both secrets.

NewWebhookVerifier and WebhookVerifier.VerifyRequest do the same without a client, and take an *http.Request directly.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL