iOS Certificates
Configure your own Apple Pass Type ID certificate so passes are signed with your own Pass Type Identifier and Team ID instead of the WalletHero platform default. The flow is: create a CSR in WalletHero, download it, generate a .cer in the Apple Developer Portal, then upload that .cer back. Private keys are generated server-side, encrypted at rest, and never returned through the management endpoints.
Auth: All certificate endpoints are workspace-scoped (path param workspaceId) and require a Bearer token. Most require workspace membership; the mutating operations (create-csr, upload-certificate, delete) additionally check the manage_workspace_settings permission inside the service, and ios/signing-data is gated on manage_workspace_settings at the route because it returns the decrypted private key.
The certificate record exposed by these endpoints (the "public info" shape) never includes the private key, CSR PEM, or certificate PEM:
interface CertificatePublicInfo {
id: string;
date_created: string;
date_updated: string;
workspace_id: string;
certificate_type: "ios" | "google";
name: string;
pass_type_identifier: string | null;
team_identifier: string | null;
subject_cn: string | null;
issuer_cn: string | null;
serial_number: string | null;
not_before: string | null;
not_after: string | null;
status: "pending_csr" | "awaiting_certificate" | "active" | "expired" | "revoked" | "invalid";
validation_error: string | null;
last_used_at: string | null;
usage_count: number;
}Status meanings: pending_csr (CSR generated, not yet downloaded), awaiting_certificate (CSR downloaded, waiting for the .cer), active (uploaded and signing verified), expired (past not_after), invalid (validation/key-mismatch failed), revoked.
List certificates
GET /wallethero-api/workspace/:workspaceId/certificates
Returns all certificates for the workspace (public info only), ordered by creation date.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier. |
Example Request
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/certificates" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": [
{
"id": "CERT_ID",
"workspace_id": "WORKSPACE_ID",
"certificate_type": "ios",
"name": "My iOS Certificate",
"pass_type_identifier": "pass.com.yourcompany.loyalty",
"team_identifier": "ABCDE12345",
"status": "active",
"not_after": "2027-07-19T00:00:00.000Z",
"usage_count": 12,
"last_used_at": "2026-06-16T08:00:00.000Z",
"validation_error": null
}
]
}SDK
const certs = await wh.workspaces.getCertificates(workspaceId);Create CSR
POST /wallethero-api/workspace/:workspaceId/certificates/create-csr
Generates a 2048-bit RSA key pair and a Certificate Signing Request. The encrypted private key and CSR are stored server-side and the certificate is created in pending_csr status. Only one active/in-progress iOS certificate is allowed per workspace — if one already exists in active, pending_csr, or awaiting_certificate status the request is rejected; delete it first.
Auth: Bearer token — workspace member with manage_workspace_settings.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name (1–255 chars). |
passTypeIdentifier | string | No | Apple Pass Type Identifier, e.g. pass.com.yourcompany.loyalty. Used as the CSR common name. |
Example Request
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/certificates/create-csr" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "My iOS Certificate", "passTypeIdentifier": "pass.com.yourcompany.loyalty" }'Response (200)
{
"data": {
"id": "CERT_ID",
"csrPem": "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----\n"
}
}SDK
const result = await wh.workspaces.createCertificateRequest(workspaceId, {
name: "My iOS Certificate",
passTypeIdentifier: "pass.com.yourcompany.loyalty",
});
// result.id, result.csrPemDownload CSR
GET /wallethero-api/workspace/:workspaceId/certificates/:id/download-csr
Returns the CSR as a downloadable application/pkcs10 file (<name>.certSigningRequest) to upload to the Apple Developer Portal. The first download transitions the certificate from pending_csr to awaiting_certificate.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier. |
id | string (UUID) | Certificate identifier. |
Example Request
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/certificates/CERT_ID/download-csr" \
-H "Authorization: Bearer YOUR_TOKEN" \
-o My_iOS_Certificate.certSigningRequestResponse (200)
Raw CSR PEM with headers:
Content-Type: application/pkcs10
Content-Disposition: attachment; filename="My_iOS_Certificate.certSigningRequest"-----BEGIN CERTIFICATE REQUEST-----
...
-----END CERTIFICATE REQUEST-----SDK
const blob = await wh.workspaces.downloadCsr(workspaceId, certId);
// Save blob as a .certSigningRequest fileUpload certificate
POST /wallethero-api/workspace/:workspaceId/certificates/:id/upload-certificate
Uploads the .cer file Apple generated from your CSR. The certificate is parsed, its public key is matched against the stored private key, and a test manifest is signed to verify the key pair works. On success the record becomes active (or expired if already past not_after) and pass_type_identifier / team_identifier are populated from the certificate. A key mismatch sets the record to invalid and returns 400. A certificate already in active status cannot be overwritten — delete it first.
Auth: Bearer token — workspace member with manage_workspace_settings.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier. |
id | string (UUID) | Certificate identifier. |
Request Body
The handler accepts the certificate in any of three forms:
| Form | Description |
|---|---|
Multipart file upload (req.file) | A .cer file field. |
JSON { "certificate": "<base64>" } | Base64-encoded .cer (a data:...;base64, prefix is stripped automatically). The SDK uses this form. |
| Raw binary body | The raw .cer bytes as the request body. |
If none is provided the request fails with 400 ("No certificate file provided. Please upload a .cer file.").
Example Request
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/certificates/CERT_ID/upload-certificate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "certificate": "BASE64_ENCODED_CER" }'Response (200)
{
"data": {
"id": "CERT_ID",
"workspace_id": "WORKSPACE_ID",
"certificate_type": "ios",
"name": "My iOS Certificate",
"pass_type_identifier": "pass.com.yourcompany.loyalty",
"team_identifier": "ABCDE12345",
"subject_cn": "Pass Type ID: pass.com.yourcompany.loyalty",
"not_before": "2026-06-16T00:00:00.000Z",
"not_after": "2027-07-19T00:00:00.000Z",
"status": "active",
"validation_error": null,
"usage_count": 0
}
}SDK
const cert = await wh.workspaces.uploadCertificate(workspaceId, certId, cerFile);
// cerFile: File | Blob — the SDK base64-encodes it and sends { certificate }Delete certificate
DELETE /wallethero-api/workspace/:workspaceId/certificates/:id
Permanently deletes the certificate (and its stored key material). After deletion the workspace reverts to the WalletHero platform certificate for signing.
Auth: Bearer token — workspace member with manage_workspace_settings.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier. |
id | string (UUID) | Certificate identifier. |
Example Request
curl -X DELETE "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/certificates/CERT_ID" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{ "success": true, "message": "Certificate deleted" }SDK
await wh.workspaces.deleteCertificate(workspaceId, certId);Validate certificate
POST /wallethero-api/workspace/:workspaceId/certificates/:id/validate
Re-checks an uploaded certificate: that a certificate PEM is present, that it has not expired, and that it still matches the stored private key. Updates the record's status (active, expired, or invalid) and validation_error accordingly, and returns the result.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier. |
id | string (UUID) | Certificate identifier. |
Example Request
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/certificates/CERT_ID/validate" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{ "data": { "valid": true, "errors": [] } }When invalid:
{ "data": { "valid": false, "errors": ["Certificate has expired"] } }SDK
const result = await wh.workspaces.validateCertificate(workspaceId, certId);
// result.valid, result.errorsGet iOS signing data (internal)
GET /wallethero-api/workspace/:workspaceId/certificates/ios/signing-data
Returns the active iOS certificate's decrypted signing material (certificate PEM, private key PEM, pass type ID, team ID) for the pass server to sign passes. Each call increments usage_count and updates last_used_at. Because it exposes the private key, it is gated on manage_workspace_settings (not plain membership). Returns { "data": null } if there is no active iOS certificate.
Auth: Bearer token — workspace member with manage_workspace_settings. Intended for internal/pass-server use.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier. |
Example Request
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/certificates/ios/signing-data" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": {
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----",
"passTypeIdentifier": "pass.com.yourcompany.loyalty",
"teamIdentifier": "ABCDE12345"
}
}No active certificate:
{ "data": null }SDK
No SDK method — internal pass-server endpoint; call the REST endpoint directly.