Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 158 additions & 128 deletions public/search-index.json

Large diffs are not rendered by default.

242 changes: 136 additions & 106 deletions public/sitemap.xml

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions scripts/build-search-index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ function stripMarkdown(markdownContent) {
.trim();
}

function canonicalizeRoute(route) {
return route.replace(/^(\/[^/]+)\/\d+-/, "$1/");
}

async function buildSearchIndex() {
const docsBasePath = path.resolve(process.cwd(), "src", "docs-app", "data");
const publicPath = path.resolve(process.cwd(), "public");
Expand All @@ -57,8 +61,9 @@ async function buildSearchIndex() {
// Construct the path relative to the 'data' directory
// e.g., src/docs-app/data/wordpress/getting-started.md -> /wordpress/getting-started
const relativePath = path.relative(docsBasePath, filePath);
const urlPath =
"/" + relativePath.replace(/\\/g, "/").replace(/\.md$/, "");
const urlPath = canonicalizeRoute(
"/" + relativePath.replace(/\\/g, "/").replace(/\.md$/, "")
);

const plainTextContent = stripMarkdown(content);

Expand Down
16 changes: 13 additions & 3 deletions scripts/build-seo-enhancements.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,30 @@ function loadSeoDataMap() {
} catch (error) {
console.error('Error loading SEO data:', error);
return {
routes: ['/', '/api/01-public-api-introduction'],
routes: ['/', '/api/public-api-introduction'],
seoByRoute: {},
};
}
}

// ---------------------------------------------------------------------------
// Map a route like "/api/01-public-api-introduction" to its markdown file
// Map a route like "/api/public-api-introduction" to its markdown file
// ---------------------------------------------------------------------------
function resolveMarkdownPath(route) {
// route starts with "/" + section + "/" + slug
// markdown lives at DATA_DIR + route + ".md"
const mdFile = path.join(DATA_DIR, `${route}.md`);
return fs.existsSync(mdFile) ? mdFile : null;
if (fs.existsSync(mdFile)) return mdFile;

const section = route.split('/')[1];
const slug = route.split('/')[2];
if (!section || !slug) return null;

const sectionDir = path.join(DATA_DIR, section);
const legacyFile = fs.readdirSync(sectionDir).find(
(file) => file.replace(/^\d+-/, '') === `${slug}.md`
);
return legacyFile ? path.join(sectionDir, legacyFile) : null;
}

// ---------------------------------------------------------------------------
Expand Down
6 changes: 4 additions & 2 deletions scripts/validate-doc-links.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ const walkMarkdownFiles = (dir) => {
return files.sort();
};

const routeForFile = (filePath) =>
`/${path.relative(docsRoot, filePath).replace(/\\/g, "/").replace(/\.md$/, "")}`;
const routeForFile = (filePath) => {
const route = `/${path.relative(docsRoot, filePath).replace(/\\/g, "/").replace(/\.md$/, "")}`;
return route.replace(/^(\/[^/]+)\/\d+-/, "$1/");
};

const findApproximateLine = (content, href) => {
const lines = content.split(/\r?\n/);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ After verifying a ticket, you can perform various actions on the ticket:
2. **Void the ticket** (invalidate) using the "void" action
3. **Return a scanned ticket** (undo scan) using the "return" action

All these operations are covered in the [Ticket Scan Actions](/api/12-private-api-ticket-scan-actions) document.
All these operations are covered in the [Ticket Scan Actions](/api/private-api-ticket-scan-actions) document.
4 changes: 2 additions & 2 deletions src/docs-app/data/api/12-private-api-ticket-scan-actions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Ticket Scan Actions

After verifying a ticket using the [ticket verification endpoint](/api/11-private-api-scan-ticket-by-code), you can perform various actions on the ticket by creating a scan history record. This document covers the three main ticket actions:
After verifying a ticket using the [ticket verification endpoint](/api/private-api-scan-ticket-by-code), you can perform various actions on the ticket by creating a scan history record. This document covers the three main ticket actions:

1. **Pickup** - Mark a ticket as used (scan for entry)
2. **Return** - Undo a previous scan (return to usable state)
Expand All @@ -26,7 +26,7 @@ Authorization: Token YOUR_API_TOKEN

The typical workflow for ticket scanning is:

1. Verify the ticket using the [verification endpoint](/api/11-private-api-scan-ticket-by-code)
1. Verify the ticket using the [verification endpoint](/api/private-api-scan-ticket-by-code)
2. Extract the ticket item ID from the verification response
3. Create a scan history record with the appropriate action

Expand Down
40 changes: 40 additions & 0 deletions src/docs-app/data/api/partner-api-customer-attribution-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Create a customer attribution token

Issue a short-lived customer attribution token for an existing partner user.

```http
POST /api/partner/customer-attribution-token/
```

The request must be authenticated with the Partner API HMAC scheme described in
the [Partner API overview](/api/partner-api-overview).

## Request body

```json
{
"partner_user_id": "customer-42"
}
```

The identity must already exist for the authenticated partner. The endpoint
returns `201`:

```json
{
"customer_attribution_token": "opaque-token",
"customer_attribution_token_expires_in_seconds": 3600
}
```

The token is valid for one hour. Pass it as `customer_attribution_token` when
creating a supported Showpass checkout basket. This lets Showpass associate the
resulting order with the partner customer.

The raw token is returned only in this response. Showpass stores a hash of the
token, so treat the raw value as a secret and do not log it.

The request returns `403` when customer attribution is not available for the
partner or venue. It returns `409` when the partner user does not exist or is
inactive. Validation errors return `400` and authentication failures return
`403`.
56 changes: 56 additions & 0 deletions src/docs-app/data/api/partner-api-order-manage-link.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Create an order-management link

Create a short-lived order-management handoff link for an attributed order.

```http
POST /api/partner/orders/manage-link/
```

The request must be authenticated with the Partner API HMAC scheme described in
the [Partner API overview](/api/partner-api-overview).

## Request body

```json
{
"partner_user_id": "customer-42",
"transaction_id": "transaction-id"
}
```

The transaction must belong to the partner user and satisfy the partner’s venue
scope. The endpoint returns `201`:

```json
{
"manage_url": "https://www.showpass.com/account/partner-login/opaque-code/",
"expires_in_seconds": 120
}
```

The link expires after 120 seconds. It can be used once. The code is stored as
a hash and cannot be reused after it is consumed or expires.

## Using the link

The `manage_url` is a browser handoff route:

```http
GET /account/partner-login/<code>/
```

When the code is valid, Showpass consumes it, creates a short-lived
order-scoped session, and redirects the customer to the order page. The route
does not return the order data directly.

An invalid, expired, already-used, or out-of-scope link redirects the customer
to the Showpass login page:

```text
/accounts/login/?next=/account/my-orders/
```

The request returns `403` when the order is outside the partner’s venue scope,
and `409` when the partner user or order cannot be found or the partner user is
inactive. Validation errors return `400` and authentication failures return
`403`.
104 changes: 104 additions & 0 deletions src/docs-app/data/api/partner-api-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Partner API overview

The Partner API is for server-to-server integrations between Showpass and a
trusted partner application. It lets a partner:

- Create or reuse a Showpass customer identity.
- Generate a short-lived customer attribution token.
- Create a short-lived order-management link for a customer’s order.
- Receive partner customer information in supported webhook events.

Use this API when your backend needs to connect customers and orders in your
system with Showpass. Keep the partner secret on your server. Do not expose it
in browser code or mobile applications.

The API base URL is:

```text
https://www.showpass.com/api/partner/
```

## Authentication

Partner credentials are provided by Showpass during Partner onboarding. Contact
your CSM to receive your Key ID and Secret. Store the Secret securely on your
server and never commit or expose it in client-side code.

Partner requests use HMAC authentication. Send these headers on every request:

| Header | Description |
| --- | --- |
| `X-Showpass-Partner-Key-Id` | Partner credential key ID. |
| `X-Showpass-Partner-Timestamp` | Unix timestamp in seconds. Requests older than five minutes or too far in the future are rejected. |
| `X-Showpass-Partner-Nonce` | A unique value for this request. A nonce cannot be reused. |
| `X-Showpass-Partner-Signature` | `sha256=` followed by the HMAC-SHA256 digest. |

Calculate the signature with the partner secret over this newline-separated
canonical value:

```text
v1
TIMESTAMP
NONCE
HTTP_METHOD
PATH_AND_QUERY
SHA256_OF_RAW_REQUEST_BODY
```

The value in `X-Showpass-Partner-Signature` is `sha256=` followed by the
lowercase hexadecimal HMAC-SHA256 digest. Sign the exact path and query string
sent to Showpass. For an empty request body, hash the empty byte string.

For example, this Python code creates the signature for a `POST` request. The
`body` value must be exactly the same bytes sent in the request:

```python
import hashlib
import hmac
import time
import uuid

partner_secret = "your-partner-secret"
body = '{"partner_user_id":"customer-42"}'
timestamp = str(int(time.time()))
nonce = str(uuid.uuid4())
path_and_query = "/api/partner/customer-attribution-token/"
body_hash = hashlib.sha256(body.encode()).hexdigest()

canonical = "\n".join([
"v1",
timestamp,
nonce,
"POST",
path_and_query,
body_hash,
])
signature = "sha256=" + hmac.new(
partner_secret.encode(),
canonical.encode(),
hashlib.sha256,
).hexdigest()
```

## Organization and venue scope

The credential identifies the partner integration and may be restricted to one
venue. A request cannot use a different venue from the credential’s venue
restriction. If no venue restriction exists, a supplied `venue_id` must refer
to an existing venue.

Partner user IDs are trimmed, normalized to lowercase, and unique within a
partner. The request is rejected when the credential is invalid or the partner
integration is inactive.

Missing or invalid authentication headers and signatures return `403`. Send
all four headers on every request, and generate a new timestamp, nonce, and
signature for each request.

## Endpoint catalog

| Method | Endpoint | Purpose |
| --- | --- | --- |
| `POST` | [`/api/partner/users/`](/api/partner-api-users) | Create or reuse a partner user identity. |
| `POST` | [`/api/partner/customer-attribution-token/`](/api/partner-api-customer-attribution-token) | Issue a short-lived customer attribution token for an existing partner identity. |
| `POST` | [`/api/partner/orders/manage-link/`](/api/partner-api-order-manage-link) | Create a short-lived order-management handoff link for an order. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint catalog stops at POST /api/partner/orders/manage-link/, but the returned URL points to GET /account/partner-login/<code>/. That browser consume route is part of the integration contract and controls one-time use, expiry, order-scoped session, and redirect behavior. Can we document it alongside the POST endpoint and state the expired or replayed-link behavior?

58 changes: 58 additions & 0 deletions src/docs-app/data/api/partner-api-users.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Create or reuse a partner user

Create or reuse a partner user identity for the authenticated partner.

```http
POST /api/partner/users/
```

The request must be authenticated with the Partner API HMAC scheme described in
the [Partner API overview](/api/partner-api-overview). The examples in the
API reference panel sign the exact raw request body sent to Showpass.

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `partner_user_id` | string | Yes | Stable partner-side user ID, up to 255 characters. It is trimmed and lowercased. |
| `email` | string | Yes | Partner user email address, up to 128 characters. |
| `email_verified` | boolean | Yes | Whether the partner has verified the email. If `true`, Showpass may link the request to an existing Showpass user with the same email. |
| `first_name` | string | No | First name, up to 32 characters. |
| `last_name` | string or null | No | Last name, up to 32 characters. |
| `phone` | string or null | No | Phone number, up to 32 characters. |
| `venue_id` | integer or null | No | Venue for this request. It must match the partner’s allowed venue when one is configured. |

## Responses

The endpoint returns `201` when it creates a new identity and `200` when it
reuses an existing identity.

```json
{
"partner_identity_id": 123,
"partner_user_id": "customer-42",
"status": "active",
"link_reason": "created_user",
"venue_id": 456
}
```

For supported venues, the response can also include
`customer_attribution_token` and
`customer_attribution_token_expires_in_seconds`.

The `link_reason` value is one of:

- `created_user`: a new Showpass user was created.
- `reused_existing`: the partner identity already existed.
- `email_auto_linked`: the request was linked to an existing Showpass user after the partner confirmed the email.

Error responses use the following status codes:

- `400 Bad Request`: the request body is missing a required field or contains
an invalid field value or type.
- `403 Forbidden`: the Partner credentials are missing or invalid, or the
requested `venue_id` is outside the partner's allowed venue scope.
- `409 Conflict`: the request is valid but conflicts with existing data. This
includes an email conflict, an inactive partner identity, or a `venue_id`
that does not identify an existing venue.
38 changes: 38 additions & 0 deletions src/docs-app/data/api/partner-api-webhooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Partner attribution in webhooks

Partner attribution adds two optional fields to an existing Showpass webhook:

```json
{
"partner_slug": "partner-name",
"partner_user_id": "customer-42"
}
```

This helps your partner application match a purchase, refund, void, or transfer
Comment thread
dawoodali-showpass marked this conversation as resolved.
to the customer in its own system.

Partner attribution can be included on these existing webhook events:

- `invoice.purchase`: a completed purchase.
- `invoice.refund`: a refund.
- `invoice.void`: a voided transaction.
- `invoice.transfer`: the recipient-side invoice and ticket records created by
a ticket transfer.
- `invoice.transferred`: the original purchaser’s invoice and ticket records
updated by a ticket transfer.

## Important

This is not a new webhook or a new webhook URL. You use the same webhook setup,
events, signatures, and delivery process described in the [Webhooks
introduction](/webhooks/webhooks-introduction).

For supported Partner integrations, Showpass adds the fields when it finds one
clear partner customer match. If there is no match or more than one possible
match, Showpass sends the normal webhook payload without these fields. The
webhook is still delivered.

For the supported event list and signature verification, see [webhook event
types](/webhooks/webhooks-event-types) and [webhook
security](/webhooks/webhooks-security).
Loading