Print Partner Extension
Print Partner Integration Guide
Overview
The Print Partner Integration enables Marq users to send their designs directly to partner print vendors for fulfillment. When a user initiates a print order, Marq securely transmits the order details along with user and product information to the partner's system using cryptographically signed messages.
This integration supports two authentication methods:
- SAML-based authentication (recommended for enterprise partners)
- JWT-based authentication (simpler implementation)
Supported File Formats
- PDF (primary format for print orders)
- JPG, PNG (for image-based products)
Partner Requirements
To integrate as a print partner, you must provide Marq with:
-
Brand Assets
- Company logo (for Marq marketplace)
- Product catalog information (if applicable)
-
Technical Details
- Print order endpoint URL (SAML destination or JWT endpoint)
- Supported file formats
- Authentication method preference (SAML or JWT)
- Public key certificate (for SAML validation) OR public key (for JWT validation)
-
Integration Contacts
- Technical contact email
- Support contact email
Contact: [email protected]
Authentication Methods
Option 1: SAML-Based Integration (Recommended)
SAML provides enterprise-grade security with cryptographic signing and is the recommended approach for high-volume print partners.
How It Works
- User initiates a print order in Marq
- Marq backend creates a signed SAML assertion containing order and user details
- Marq frontend receives the SAML response and submits it to your endpoint via POST
- Your system validates the SAML signature using Marq's public certificate
- Your system processes the order using the authenticated data
SAML Request Format
The SAML response will be submitted to your endpoint as a POST request:
POST https://your-print-partner.com/api/print-orders
Content-Type: application/x-www-form-urlencoded
SAMLResponse=<base64-encoded-saml-response>
SAML Response Structure
The SAML response is a base64-encoded XML document containing a signed assertion. After decoding and parsing, you'll find:
SAML Response Element:
<saml2p:Response
xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
ID="<unique-response-id>"
Destination="https://your-print-partner.com/api/print-orders"
IssueInstant="<timestamp>"
Version="2.0">
<saml2:Assertion>
...
</saml2:Assertion>
</saml2p:Response>
SAML Assertion (contains the print order data):
<saml2:Assertion
xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
ID="<unique-assertion-id>"
IssueInstant="<timestamp>"
Version="2.0">
<!-- Digital signature for verification -->
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
...
</ds:Signature>
<!-- Validity conditions (10 minute window) -->
<saml2:Conditions
NotBefore="<timestamp>"
NotOnOrAfter="<timestamp-plus-10-minutes>">
</saml2:Conditions>
<!-- Order and user attributes -->
<saml2:AttributeStatement>
<!-- Required Attributes -->
<saml2:Attribute Name="externalOrderId">
<saml2:AttributeValue>marq-01234567-89ab-cdef-0123-456789abcdef</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="pdfUrl">
<saml2:AttributeValue>https://marq-assets.s3.amazonaws.com/signed-url...</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="EmailAddress">
<saml2:AttributeValue>[email protected]</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="FirstName">
<saml2:AttributeValue>John</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="LastName">
<saml2:AttributeValue>Smith</saml2:AttributeValue>
</saml2:Attribute>
<!-- Optional Attributes (if configured) -->
<saml2:Attribute Name="productid">
<saml2:AttributeValue>PRODUCT-SKU-123</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="templatekey">
<saml2:AttributeValue>TEMPLATE-ABC-456</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="UserID">
<saml2:AttributeValue>user-id-in-your-system</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="OfficeID">
<saml2:AttributeValue>office-id-in-your-system</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
SAML Attribute Definitions
| Attribute | Type | Required | Description |
|---|---|---|---|
externalOrderId | String | Yes | Unique order identifier from Marq (format: marq-{UUID}) |
pdfUrl | String | Yes | Pre-signed URL to download the PDF file (valid for 1 hour) |
EmailAddress | String | Yes | User's email address in Marq |
FirstName | String | Yes | User's first name |
LastName | String | Yes | User's last name |
productid | String | No | Your product SKU/ID (if product catalog is configured) |
templatekey | String | No | Your template identifier (if template mapping is configured) |
UserID | String | No | User's identifier in your system (configured per user) |
OfficeID | String | No | Office/location identifier in your system (configured per user) |
Validating SAML Assertions
Critical Security Steps:
- Decode the Base64 SAMLResponse parameter
- Parse the XML to extract the assertion
- Verify the digital signature using Marq's public certificate (provided during onboarding)
- Check the timestamp validity - Assertions are valid for 10 minutes
- Verify the Destination matches your endpoint URL
- Extract and process the order attributes
Example validation flow (pseudocode):
def validate_saml_order(saml_response_base64):
# 1. Decode
saml_xml = base64.b64decode(saml_response_base64)
# 2. Parse XML
doc = parse_xml(saml_xml)
assertion = doc.find('Assertion')
# 3. Verify signature using Marq's public certificate
if not verify_signature(assertion, MARQ_PUBLIC_CERT):
raise SecurityError("Invalid signature")
# 4. Check timestamp
conditions = assertion.find('Conditions')
not_before = parse_datetime(conditions.get('NotBefore'))
not_on_or_after = parse_datetime(conditions.get('NotOnOrAfter'))
now = datetime.utcnow()
if now < not_before or now >= not_on_or_after:
raise SecurityError("Assertion expired or not yet valid")
# 5. Verify destination
response = doc.find('Response')
if response.get('Destination') != YOUR_ENDPOINT_URL:
raise SecurityError("Invalid destination")
# 6. Extract attributes
attributes = {}
for attr in assertion.findall('.//Attribute'):
name = attr.get('Name')
value = attr.find('AttributeValue').text
attributes[name] = value
return attributes
Libraries for SAML validation:
- Python:
python3-saml,pysaml2 - Node.js:
saml2-js,passport-saml - Java: OpenSAML library
- .NET:
ComponentSpace.Saml2
Option 2: JWT-Based Integration
JWT provides a simpler integration path while still maintaining secure authentication through cryptographic signatures.
How It Works
- User initiates a print order in Marq
- Marq backend creates a JWT token containing order and user details
- User is redirected to your endpoint with the token as a query parameter
- Your system validates the JWT signature using Marq's public key
- Your system processes the order using the authenticated data
JWT Request Format
Users will be redirected to your endpoint with a JWT token:
https://your-print-partner.com/orders?token={jwt-token}
JWT Structure
The JWT token consists of three parts separated by dots (.):
Header:
{
"alg": "RS256",
"typ": "JWT"
}
Payload:
{
"jti": "marq-01234567-89ab-cdef-0123-456789abcdef",
"iat": 1709234567,
"exp": 1709235167,
"userId": "[email protected]",
"firstName": "John",
"lastName": "Smith",
"projectId": "abc123-document-id",
"projectName": "Marketing Flyer - Spring 2024",
"downloadUrl": "https://marq-assets.s3.amazonaws.com/signed-url...",
"contentLength": 2456789,
"mimeType": "application/pdf",
"productId": "PRODUCT-SKU-123",
"templateKey": "TEMPLATE-ABC-456",
"userIdAtPartner": "user-id-in-your-system",
"officeIdAtPartner": "office-id-in-your-system"
}
Signature:
Cryptographically signed using Marq's RSA private key (you validate with the public key).
JWT Field Definitions
| Field | Type | Required | Description |
|---|---|---|---|
jti | String | Yes | JWT ID - unique order identifier (format: marq-{UUID}) |
iat | Number | Yes | Issued At - Unix timestamp when token was created |
exp | Number | Yes | Expiration - Unix timestamp when token expires (10 minutes after iat) |
userId | String | Yes | User's email address in Marq |
firstName | String | Yes | User's first name |
lastName | String | Yes | User's last name |
projectId | String | Yes | Unique identifier for the design in Marq |
projectName | String | Yes | Name of the design/document |
downloadUrl | String | Yes | Pre-signed URL to download the PDF (valid for 1 hour) |
contentLength | Number | Yes | File size in bytes |
mimeType | String | Yes | Content type (typically application/pdf) |
productId | String | No | Your product SKU/ID (if product catalog is configured) |
templateKey | String | No | Your template identifier (if template mapping is configured) |
userIdAtPartner | String | No | User's identifier in your system (configured per user) |
officeIdAtPartner | String | No | Office/location identifier in your system (configured per user) |
Validating JWT Tokens
Critical Security Steps:
- Verify the JWT signature using Marq's public key
- Check token expiration (
expclaim) - Verify token freshness (
iatclaim) - Validate the
jtiis unique (prevent replay attacks) - Extract and process the order claims
Example validation flow (pseudocode):
const jwt = require('jsonwebtoken');
function validateMarqJWT(token, marqPublicKey) {
try {
// Verify and decode in one step
const decoded = jwt.verify(token, marqPublicKey, {
algorithms: ['RS256'],
clockTolerance: 30 // Allow 30 second clock skew
});
// Additional validation
const now = Math.floor(Date.now() / 1000);
// Check expiration (library already does this, but explicit check)
if (decoded.exp < now) {
throw new Error('Token expired');
}
// Check token wasn't issued in the future
if (decoded.iat > now + 60) {
throw new Error('Token issued in the future');
}
// Verify required fields exist
const requiredFields = [
'jti', 'userId', 'firstName', 'lastName',
'downloadUrl', 'projectId', 'projectName'
];
for (const field of requiredFields) {
if (!decoded[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
// Check for replay attack (you should track used JTIs)
if (await hasBeenUsed(decoded.jti)) {
throw new Error('Token has already been used');
}
// Mark token as used
await markTokenAsUsed(decoded.jti, decoded.exp);
return decoded;
} catch (error) {
console.error('JWT validation failed:', error);
throw error;
}
}
// Usage
app.get('/orders', async (req, res) => {
try {
const token = req.query.token;
const orderData = validateMarqJWT(token, MARQ_PUBLIC_KEY);
// Process the order
const orderId = await createPrintOrder(orderData);
res.redirect(`/orders/${orderId}`);
} catch (error) {
res.status(401).send('Invalid or expired token');
}
});
Libraries for JWT validation:
- Node.js:
jsonwebtoken - Python:
PyJWT - Java:
java-jwt(Auth0) - PHP:
firebase/php-jwt - Ruby:
ruby-jwt - Go:
golang-jwt/jwt
Downloading the Print File
Both authentication methods provide a downloadUrl / pdfUrl field containing a pre-signed URL to download the print file.
Important Notes:
- The URL is valid for 1 hour from token/assertion generation
- The URL is pre-signed - no additional authentication is needed
- Download the file immediately upon receiving the order
- The file should be downloaded server-side (not client-side)
- Store the file in your system associated with the order ID
Example download implementation:
import requests
def download_print_file(pdf_url, order_id):
# Download the file
response = requests.get(pdf_url, stream=True)
if response.status_code != 200:
raise Exception(f"Failed to download file: {response.status_code}")
# Verify content type
content_type = response.headers.get('Content-Type')
if content_type != 'application/pdf':
raise Exception(f"Unexpected content type: {content_type}")
# Save to your storage system
file_path = f"print-orders/{order_id}.pdf"
with open(file_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return file_path
User and Account Mapping
Optional Identifiers
If your system requires mapping Marq users to your existing user/office structure, you can configure:
- User ID mapping - Maps Marq users to user IDs in your system
- Office ID mapping - Maps Marq users to office/location IDs in your system
These mappings are configured on a per-user basis in Marq and included in the order payload when set.
Default Behavior:
- If
UserID/userIdAtPartneris not configured, it will default to the user's email address - If
OfficeID/officeIdAtPartneris not configured, it may be omitted or set to a default office ID (agreed upon during integration setup)
Product Catalog Integration
If you provide a product catalog, Marq can pass product-specific information with each order:
- Product ID (
productid/productId) - Your SKU or product identifier - Template Key (
templatekey/templateKey) - Your template identifier (if applicable)
This allows users to select specific products (business cards, flyers, banners, etc.) from your catalog within Marq, and the selection is passed with the order.
Security Best Practices
For Both Authentication Methods
- Always validate signatures - Never trust unsigned data
- Check timestamp/expiration - Reject expired tokens/assertions
- Use HTTPS only - All endpoints must use TLS 1.2 or higher
- Validate the source - Verify tokens/assertions came from Marq
- Log authentication failures - Monitor for potential attacks
- Rate limit your endpoints - Prevent abuse
SAML-Specific
- Store Marq's certificate securely - Protect the public certificate
- Verify the Destination attribute - Prevent token reuse on wrong endpoints
- Implement time skew tolerance - Allow 30-60 seconds for clock differences
- Reject unsigned assertions - All assertions must be signed
JWT-Specific
- Prevent replay attacks - Track used JTI values until expiration
- Store Marq's public key securely - Protect the verification key
- Validate the algorithm - Only accept RS256
- Check all timestamp claims - Both
iatandexp
Testing Your Integration
Marq will provide test credentials and a sandbox environment for integration testing.
Test Checklist:
- Successfully validate SAML signature / JWT signature
- Correctly extract all required fields
- Successfully download print file from provided URL
- Handle expired tokens/assertions appropriately
- Handle invalid signatures appropriately
- Handle missing required fields appropriately
- Successfully create print orders in your system
- Test with various product types (if using catalog)
- Test with user/office ID mappings (if applicable)
Error Handling
Your endpoint should return appropriate HTTP status codes:
| Status | Use Case |
|---|---|
200 OK | Order successfully received and processed |
400 Bad Request | Invalid token/assertion format or missing required fields |
401 Unauthorized | Invalid signature or expired token/assertion |
404 Not Found | Product ID or template key not found (if applicable) |
500 Internal Server Error | Server-side processing error |
User Experience:
- After successful validation, redirect users to an order confirmation page
- Display clear error messages for invalid tokens
- Provide support contact information for issues
Integration Support
For questions or assistance with your print partner integration:
Email: [email protected]
Include in your inquiry:
- Partner name and contact information
- Preferred authentication method (SAML or JWT)
- Technical questions or implementation issues
- Timeline for integration completion
Appendix: Public Key/Certificate Exchange
SAML Certificate Format
Marq will provide an X.509 certificate in PEM format:
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKL0UG+mRZ2nMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRAwDgYDVQQHDAdTYW4gSm9zZTEP
... (certificate data) ...
-----END CERTIFICATE-----
JWT Public Key Format
Marq will provide an RSA public key in PEM format:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
... (public key data) ...
-----END PUBLIC KEY-----
Store these securely and use them to validate all incoming requests from Marq.
Updated 1 day ago
