# Get started

Learn about authentication, permissions, error handling, and API key management.

## OpenAPI specification

The complete API schema is available as a single bundled file. It includes all endpoints, request and response structures, and field definitions. To integrate programmatically, download the specification in the preferred format, instead of parsing the rendered API reference:

div
a
svg
g
path
YAML
    
a
svg
g
path
JSON
    
## Authentication and security

The Solidgate API v2 uses a simplified **Bearer token** authentication model for outbound API calls. You no longer need to manage request signatures or multiple key sets when calling the API. Incoming webhook deliveries still require [HMAC signature verification](#webhook-validation).

br
**API key**

- **Format:** Key IDs use the prefix `akey_xxx`, secrets use the prefix `asec_xxx`.
- **Method:** Include the key in the `Authorization` header of every request.


Example header

```text
Authorization: Bearer your_secret_here
```

br
**Key flexibility**

API keys are decoupled from channels.

- **Account level:** By default, keys exist at the account level with access to all channels.
- **Channel-bound:** A key that can be restricted to one or many specific channels.


## API structure and conventions

The API follows a strict structural pattern to keep behavior predictable.

br
**Endpoint format**

All operations use the **POST** method.

```text
POST {host}/{version}/{resources}/{action}
```

- **Host:** `https://api.solidgate.com`
- **Version:** `v2`
- **Resources:** Plural form of the domain model, for example, `api-keys`, `payments`.
- **Action:** Operation name, for example, `create`, `list`, or `rotate`.


br
**Data formatting**

- **Body:** JSON
- **Property names:** `snake_case`
- **Enum values:** `UPPER_CASE`


br
## Scopes and permissions

Fine-grained access control follows the principle of least privilege. Keys can be restricted by **channel** and **permission**.

br
## API key management

Manage keys via **Solidgate Hub** or **API v2**. Hub access is available to Merchant Admin and Developer roles. Navigate to the **Developers** section and then select **API v2**.

br
**Key rotation with zero downtime**

To maintain security without service interruption, use **Rotation**.

ol
li
strong
Initiate
ul
li
strong
API:
Call 
code
/rotate
and set the rotation period in seconds
li
strong
Hub:
Click 
strong
Rotate
for the API key
li
strong
Overlap
br
During this period, both old and new secrets remain valid
      
li
strong
Expiry
br
After the period ends, the old secret is automatically deactivated
      
br
**API key operations**

All operations require an account-level API key marked in Hub as **Applies to all channels**.

| Host | Domain |
|  --- | --- |
| `POST /v2/api-keys/create` | **[Create API key](https://api.solidgate.com/v2/api-keys/create)** |
| `POST /v2/api-keys/list` | **[List API keys](https://api.solidgate.com/v2/api-keys/list)** |
| `POST /v2/api-keys/get` | **[Get API key details](https://api.solidgate.com/v2/api-keys/get)** |
| `POST /v2/api-keys/rotate` | **[Rotate API key](https://api.solidgate.com/v2/api-keys/rotate)** |


## Error handling

The API uses standard HTTP status codes. All errors return the same JSON envelope, for example:

```json
{"code": "PERMISSION_DENIED", "message": "Permission denied"}
```

Some responses include a `context` object with structured details. Every response carries a `request-id` header, share it with support when reporting issues.

| Status | Code | Description |
|  --- | --- | --- |
| `400` | `VALIDATION` | Malformed JSON or invalid field constraints. `context.constraints` lists per-field failures. |
| `401` | `UNAUTHENTICATED` | Invalid or missing API key. |
| `403` | `PERMISSION_DENIED` | Key lacks the required scope, or channel-bound key calling outside its channels. |
| `404` | `NOT_FOUND` | Resource or endpoint does not exist. |
| `422` | domain-specific | Request conflicts with current business or system state (e.g., `ENDPOINT_ALREADY_EXISTS`). |
| `429` | `RATE_LIMIT` | Quota exhausted. Check `context.next_try_at`. |
| `500` | `INTERNAL` | Server-side failure. |


## Rate limits

Rate limiting controls the frequency at which requests are made to API endpoints within specific time periods.

It helps protect against service overload while ensuring consistent performance for all clients. Exceeding limits results in a `429 Too many requests` error response.

br
**API usage limits**

Solidgate returns the `429` error response when necessary to protect legitimate merchant traffic.

Rate limits differ by endpoint based on operational and reliability needs. The Solidgate team continuously monitors system performance and may adjust these limits as needed to maintain optimal service quality.

For endpoint-specific rate limit information, visit the **Developers** section in the Solidgate Hub, which is updated as changes occur.

br
**Handle rate limits**

You can handle rate limiting by monitoring for the `429 Too many requests` error response. Effective handling combines retries and overall request flow control.

A widely used approach for handling rate limit error responses is implementing exponential backoff with jitter. This method retries requests using short initial delays that increase after each failure. Introducing randomization, or jitter, helps avoid conflicts caused by multiple clients retrying simultaneously.

While retries are useful, a significant improvement comes from regulating request flow across the entire application. The token bucket is standard practice for this purpose. It allows short bursts of requests while enforcing an average request rate over time, reducing traffic spikes and improving overall stability.

## Webhook validation

Webhook event security uses a Base64-encoded HMAC-SHA256 signature generated with your webhook endpoint secret. Each notification includes a `signature` value in the headers.

- `signature` – a Base64-encoded HMAC-SHA256 digest of the raw request body, signed with your webhook endpoint secret.


Unlike v1, webhook deliveries do not include a public key in the headers. Identify the correct webhook endpoint secret using the endpoint that received the webhook.

### Verify a signature

1. Determine which endpoint received the webhook, and look up that endpoint's secret.
2. Generate a signature from the raw request body using the `generateSignature` function, which must return the Base64-encoded digest.
3. Compare your generated signature to the `signature` header value. Reject the request if they do not match.


Use the raw JSON body exactly as received, with no changes. Serializers, URL encoding, or reformatting can change the byte structure and produce a different hash, causing a valid webhook to fail verification.

style
.sg-prompt-accordion > div > :first-child { margin-top: 0; }
    .sg-prompt-accordion .sg-prompt-chevron { display:inline-block; transition:transform .15s; }
    .sg-prompt-accordion[open] .sg-prompt-chevron { transform: rotate(90deg); }
    
details
summary
span
span
▸
AI prompt to verify v2 webhook signatures
button
Copy
div
Implement Solidgate v2 webhook signature verification for my integration.

Context: Solidgate v2 webhooks carry one verification header, `signature` (a base64-encoded HMAC-SHA256 of the raw body, signed with a per-endpoint secret prefixed `wsec_`). Everything else, `event_id`, `event_type`, `occurred_at`, and the `data` payload, travels in the JSON body, not headers. Each webhook endpoint I registered has its own secret. There is no public key or endpoint identifier in the request itself, so the receiving URL tells me which stored secret to use.

Algorithm to implement:

1. Look up the secret (`wsec_...`) I stored for the specific endpoint URL that received this request.
2. Read the raw request body as bytes, before any JSON parsing or reserialization. The exact byte sequence affects the hash.
3. Compute HMAC-SHA256 of the raw body using that secret.
4. Base64-encode the raw digest bytes directly. There is no hex step. This differs from Solidgate v1, which hex-encodes first, so do not reuse v1 code here.
5. Compare the result to the `signature` header using a constant-time comparison. Use `hash_equals` in PHP, `hmac.compare_digest` in Python, `crypto.timingSafeEqual` in Node.js, `hmac.Equal` in Go, `MessageDigest.isEqual` in Java or Kotlin, or `CryptographicOperations.FixedTimeEquals` in C#. Do not use `==` or `.equals()`.
6. If verification fails, retry with my previous secret before rejecting, in case I am inside a rotation overlap window. Old and new secrets both stay valid during rotation.
7. Reject the request without processing it if verification fails against both secrets.


Also implement:

- Idempotency: deduplicate using the body's `event_id` field. Store processed event IDs for at least one week (7 days) and skip duplicates seen within that window.
- Delivery order: do not assume ordering. Delivery order is not guaranteed, so the same event can arrive more than once and out of sequence.
- Ordering (if needed): if my integration needs to apply events in the correct order, sequence them using the body's `occurred_at` field on a best-effort basis. This field reflects when the event occurred, not when it was delivered, so treat it as a hint rather than a guarantee.
- Fast response: verify the signature, durably enqueue the payload, then return a 200 status immediately. Process the queued payload asynchronously in a background worker rather than running business logic inside the request handler.


After generating the code, list each requirement above (secret lookup, signature algorithm, rotation fallback, idempotency, ordering, fast response) and state how the implementation satisfies it. If any requirement is not addressed, say so explicitly instead of omitting it.

Write this in my language and framework. Ask me which one if it is not clear from my codebase, and match my project's existing conventions for reading raw request bodies and structuring webhook handlers.

### Delivery payload

Every v2 webhook payload includes the following fields.

| Field | Type | Description | Example |
|  --- | --- | --- | --- |
| `event_id` | `string` | Unique ID for the event. Use it to deduplicate deliveries. | `3f1c8a52-0b6d-4d3e-9c2a-1e8b7f4a90d1` |
| `event_type` | `string` | The type of event that occurred. | `SUBSCRIPTION_CREATED` |
| `occurred_at` | `string` (date-time) | Date and time when the event occurred. Use it to order events chronologically. | `2026-04-03T16:18:40.000000Z` |
| `data` | `object` | The event payload. Structure depends on `event_type`. | — |


Unlike v1, this metadata is sent in the payload rather than the headers. `signature` is the only header v2 sends.

### Best practices

- **Prevent replay attacks**: Check the `occurred_at` field in the payload. Reject deliveries with a timestamp older than 5 minutes.
- **Process asynchronously**: Return a `200 OK` response immediately after signature verification, then handle the payload in a background queue. This avoids timeout failures during traffic spikes.
- **Ensure idempotency**: Delivery order is not guaranteed, and the same event may arrive more than once with the same `event_id`. Store processed event IDs for at least one week, and skip any duplicate.


## Backward compatibility

Breaking changes can impact existing integrations and require adjustments. These are marked with a **breaking changes** badge in the changelog and include:

| Category | Change |
|  --- | --- |
| **Operation removal** | Removing an API operation. |
| **Request** | Remove or rename a field, make optional fields required, remove `oneOf`. |
| **Response** | Remove or rename a field, change HTTP status code, remove `oneOf`. |
| **Type changes** | Change request or response data types. |
| **HTTP headers** | Add required headers or remove existing ones. |
| **Enum updates** | Remove enum values. |
| **Errors** | Change existing error codes. |
| **Validation rules** | Add stricter or new rules. |
| **Authentication and authorization** | Change requirements. |


Non-breaking changes modifications do not affect existing integrations and ensure backward compatibility:

| Category | Change |
|  --- | --- |
| **Request** | Add new optional fields, change required fields to optional. |
| **Response** | Add new optional fields, change optional fields to required. |
| **HTTP headers** | Add new optional headers, change header case. |
| **Field length** | Expand maximum length. |
| **Identifier format** | Change prefixes or formatting. |
| **Webhook events** | Add new opt-in event types. |
| **Webhook schema** | Add new fields. |
| **Rate limiting** | Changes communicated at least one month in advance. |


For help, contact us.