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:

  1. Brand Assets

    • Company logo (for Marq marketplace)
    • Product catalog information (if applicable)
  2. 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)
  3. 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

  1. User initiates a print order in Marq
  2. Marq backend creates a signed SAML assertion containing order and user details
  3. Marq frontend receives the SAML response and submits it to your endpoint via POST
  4. Your system validates the SAML signature using Marq's public certificate
  5. 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

AttributeTypeRequiredDescription
externalOrderIdStringYesUnique order identifier from Marq (format: marq-{UUID})
pdfUrlStringYesPre-signed URL to download the PDF file (valid for 1 hour)
EmailAddressStringYesUser's email address in Marq
FirstNameStringYesUser's first name
LastNameStringYesUser's last name
productidStringNoYour product SKU/ID (if product catalog is configured)
templatekeyStringNoYour template identifier (if template mapping is configured)
UserIDStringNoUser's identifier in your system (configured per user)
OfficeIDStringNoOffice/location identifier in your system (configured per user)

Validating SAML Assertions

Critical Security Steps:

  1. Decode the Base64 SAMLResponse parameter
  2. Parse the XML to extract the assertion
  3. Verify the digital signature using Marq's public certificate (provided during onboarding)
  4. Check the timestamp validity - Assertions are valid for 10 minutes
  5. Verify the Destination matches your endpoint URL
  6. 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

  1. User initiates a print order in Marq
  2. Marq backend creates a JWT token containing order and user details
  3. User is redirected to your endpoint with the token as a query parameter
  4. Your system validates the JWT signature using Marq's public key
  5. 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

FieldTypeRequiredDescription
jtiStringYesJWT ID - unique order identifier (format: marq-{UUID})
iatNumberYesIssued At - Unix timestamp when token was created
expNumberYesExpiration - Unix timestamp when token expires (10 minutes after iat)
userIdStringYesUser's email address in Marq
firstNameStringYesUser's first name
lastNameStringYesUser's last name
projectIdStringYesUnique identifier for the design in Marq
projectNameStringYesName of the design/document
downloadUrlStringYesPre-signed URL to download the PDF (valid for 1 hour)
contentLengthNumberYesFile size in bytes
mimeTypeStringYesContent type (typically application/pdf)
productIdStringNoYour product SKU/ID (if product catalog is configured)
templateKeyStringNoYour template identifier (if template mapping is configured)
userIdAtPartnerStringNoUser's identifier in your system (configured per user)
officeIdAtPartnerStringNoOffice/location identifier in your system (configured per user)

Validating JWT Tokens

Critical Security Steps:

  1. Verify the JWT signature using Marq's public key
  2. Check token expiration (exp claim)
  3. Verify token freshness (iat claim)
  4. Validate the jti is unique (prevent replay attacks)
  5. 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 / userIdAtPartner is not configured, it will default to the user's email address
  • If OfficeID / officeIdAtPartner is 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

  1. Always validate signatures - Never trust unsigned data
  2. Check timestamp/expiration - Reject expired tokens/assertions
  3. Use HTTPS only - All endpoints must use TLS 1.2 or higher
  4. Validate the source - Verify tokens/assertions came from Marq
  5. Log authentication failures - Monitor for potential attacks
  6. Rate limit your endpoints - Prevent abuse

SAML-Specific

  1. Store Marq's certificate securely - Protect the public certificate
  2. Verify the Destination attribute - Prevent token reuse on wrong endpoints
  3. Implement time skew tolerance - Allow 30-60 seconds for clock differences
  4. Reject unsigned assertions - All assertions must be signed

JWT-Specific

  1. Prevent replay attacks - Track used JTI values until expiration
  2. Store Marq's public key securely - Protect the verification key
  3. Validate the algorithm - Only accept RS256
  4. Check all timestamp claims - Both iat and exp

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:

StatusUse Case
200 OKOrder successfully received and processed
400 Bad RequestInvalid token/assertion format or missing required fields
401 UnauthorizedInvalid signature or expired token/assertion
404 Not FoundProduct ID or template key not found (if applicable)
500 Internal Server ErrorServer-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.