Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Backend Integrations & the Spree Commerce Hub

Brian Quinn
February 26, 2014

Backend Integrations & the Spree Commerce Hub

SpreeConf 2014, New York City

Brian Quinn

February 26, 2014
Tweet

More Decks by Brian Quinn

Other Decks in Programming

Transcript

  1. Backend Integrations & the
    Spree Commerce Hub
    SpreeConf 2014, NYC

    View full-size slide

  2. Brian Quinn
    CTO & Co founder of Spree Commerce, Inc.
    Twitter: @briandq Github: BDQ

    View full-size slide

  3. What is the hub?

    View full-size slide

  4. A platform
    to simplify the backend logistics for commerce businesses.

    View full-size slide

  5. A platform
    to decouple the backend processes from the storefront.

    View full-size slide

  6. A suite of integrations

    View full-size slide

  7. A suite of integrations
    to get your commerce business moving faster.

    View full-size slide

  8. A suite of tools

    View full-size slide

  9. A suite of tools
    to help you monitor and manage your integrations.

    View full-size slide

  10. A suite of tools
    to help you implement custom integrations.

    View full-size slide

  11. A peace of mind

    View full-size slide

  12. A peace of mind
    that everything is happening as it should.

    View full-size slide

  13. A peace of mind
    that everything is happening as it should.
    OR WE’LL COME FIND YOU!

    View full-size slide

  14. A peace of mind
    that everything is happening as it should.
    OR WE’LL COME FIND YOU!

    View full-size slide

  15. A peace of mind
    that everything is happening as it should.
    OR WE’LL COME HELP YOU!

    View full-size slide

  16. What problems does it solve?
    The Acme Widgets Store

    View full-size slide

  17. What problems does it solve?
    The Acme Widgets Store
    ➡ Third Party Logistics

    View full-size slide

  18. What problems does it solve?
    The Acme Widgets Store
    ➡ Third Party Logistics
    ➡ Accounting Application

    View full-size slide

  19. What problems does it solve?
    The Acme Widgets Store
    ➡ Third Party Logistics
    ➡ Accounting Application
    ➡ Multi-channel Sales

    View full-size slide

  20. What problems does it solve?
    The Acme Widgets Store
    ➡ Third Party Logistics
    ➡ Accounting Application
    ➡ Multi-channel Sales
    ➡ Customer Service

    View full-size slide

  21. What problems does it solve?
    The Acme Widgets Store
    ➡ Third Party Logistics
    ➡ Accounting Application
    ➡ Multi-channel Sales
    ➡ Customer Service
    ➡ Analytics & Marketing

    View full-size slide

  22. What problems does it solve?
    The Acme Widgets Store
    ➡ Third Party Logistics
    ➡ Accounting Application
    ➡ Multi-channel Sales
    ➡ Customer Service
    ➡ Analytics & Marketing

    View full-size slide

  23. What problems does it solve?
    The Acme Widgets Store
    ➡ Third Party Logistics
    ➡ Accounting Application
    ➡ Multi-channel Sales
    ➡ Customer Service
    ➡ Analytics & Marketing
    And much more!

    View full-size slide

  24. History - Timeline

    View full-size slide

  25. History - Timeline
    ➡ Version 1 went into production late 2012

    View full-size slide

  26. History - Timeline
    ➡ Version 1 went into production late 2012
    ➡ Version 2..4 deployed up to Q1 2013

    View full-size slide

  27. History - Timeline
    ➡ Version 1 went into production late 2012
    ➡ Version 2..4 deployed up to Q1 2013
    ➡ Version 5 - SpreeConf DC - May 2013

    View full-size slide

  28. History - Timeline
    ➡ Version 1 went into production late 2012
    ➡ Version 2..4 deployed up to Q1 2013
    ➡ Version 5 - SpreeConf DC - May 2013
    ➡ called the ‘Integrator’

    View full-size slide

  29. History - Timeline
    ➡ Version 1 went into production late 2012
    ➡ Version 2..4 deployed up to Q1 2013
    ➡ Version 5 - SpreeConf DC - May 2013
    ➡ called the ‘Integrator’
    ➡ Version 6 - SpreeConf NYC - February 2014

    View full-size slide

  30. History - Timeline
    ➡ Version 1 went into production late 2012
    ➡ Version 2..4 deployed up to Q1 2013
    ➡ Version 5 - SpreeConf DC - May 2013
    ➡ called the ‘Integrator’
    ➡ Version 6 - SpreeConf NYC - February 2014
    ➡ called the ‘Spree Commerce Hub’

    View full-size slide

  31. History - Integrations
    ➡ NetSuite
    ➡ OpenERP
    ➡ QuickBooks
    ➡ Quiet Logistics
    ➡ Shipwire
    ➡ DotCom
    ➡ PCH International
    ➡ Fosdick
    ➡ VPD
    ➡ RLM
    ➡ Simparel
    ➡ Twilio
    ➡ Zendesk
    ➡ Desk
    ➡ Tender
    ➡ Mandrill
    ➡ Exact Target
    ➡ SMTP

    View full-size slide

  32. We’re off to a great start

    View full-size slide

  33. We’re off to a great start
    and there’s lots more to come.

    View full-size slide

  34. Challenge 1:
    How do we make things easier for everyone?

    View full-size slide

  35. Challenge 2:
    How do we make it accessible to everyone?

    View full-size slide

  36. Simplified API
    The API for commerce

    View full-size slide

  37. Three core terms

    View full-size slide

  38. Three core terms
    Object
    ➡ A JSON representation of an order, product,
    customer, shipment, return, etc.

    View full-size slide

  39. Three core terms
    Object
    ➡ A JSON representation of an order, product,
    customer, shipment, return, etc.
    Event
    ➡ An interesting change to an object.

    View full-size slide

  40. Three core terms
    Object
    ➡ A JSON representation of an order, product,
    customer, shipment, return, etc.
    Event
    ➡ An interesting change to an object.
    Webhook
    ➡ HTTP POST to predefined URL.

    View full-size slide

  41. Common Use Cases
    ➡ Third Party Logistics
    ➡ Accounting & ERP
    ➡ Multi channel sales
    ➡ Communications
    ➡ Customer Service

    View full-size slide

  42. Third Party Logistics
    Use Case: “I want to tell my 3PL when
    there’s a new shipment to fulfill.”
    ➡ Object: Shipment
    ➡ Event: shipment:ready
    ➡ Webhook: /add_shipment

    View full-size slide

  43. Third Party Logistics
    Use Case: “I want to tell my 3PL when
    there’s a new shipment to fulfill.”
    ➡ Object: Shipment
    ➡ Event: shipment:ready
    ➡ Webhook: /add_shipment

    View full-size slide

  44. Third Party Logistics
    Use Case: “I want to tell my 3PL when
    there’s a new shipment to fulfill.”
    ➡ Object: Shipment
    ➡ Event: shipment:ready
    ➡ Webhook: /add_shipment

    View full-size slide

  45. Third Party Logistics
    Use Case: “I want to update my store
    when a shipment has dispatched.”
    ➡ Object: Shipment
    ➡ Event: schedule
    ➡ Webhook: /get_shipments

    View full-size slide

  46. Third Party Logistics
    Use Case: “I want to update my store
    with my 3PL’s stock levels.”
    ➡ Object: Inventory
    ➡ Event: schedule
    ➡ Webhook: /get_inventory

    View full-size slide

  47. Accounting & ERP
    Use Case: “I want keep my accounts up
    to date with all orders my store front.”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: /add_order

    View full-size slide

  48. Accounting & ERP
    Use Case: “I want keep my accounts up
    to date with all orders my store front.”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: /add_order

    View full-size slide

  49. Accounting & ERP
    Use Case: “I want keep my accounts up
    to date with all orders my store front.”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: /add_order

    View full-size slide

  50. Accounting & ERP
    ➡ Object: Order
    ➡ Event: order:updated
    ➡ Webhook: /update_order
    Use Case: “I want keep my accounts up
    to date with all orders my store front.”

    View full-size slide

  51. Accounting & ERP
    Use Case: “I need to import Products
    from my ERP to my store front.”
    ➡ Object: Product
    ➡ Event: schedule
    ➡ Webhook: /get_products

    View full-size slide

  52. Multi Channel Sales
    Use Case: “I’d like to consolidate & process
    all my orders on a single platform.”
    ➡ Object: Order
    ➡ Event: schedule
    ➡ Webhook: /get_orders

    View full-size slide

  53. Multi Channel Sales
    Use Case: “I’d like to consolidate & process
    all my orders on a single platform.”
    ➡ Object: Order
    ➡ Event: schedule
    ➡ Webhook: /get_orders

    View full-size slide

  54. Use Case: “I want to keep stock levels
    in-sync across all my sales channels.”
    ➡ Object: Inventory
    ➡ Event: inventory:updated
    ➡ Webhook: /set_inventory
    Multi Channel Sales

    View full-size slide

  55. Communications
    Use Case: “I want to send a user an
    email after they complete an order”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: /send_email

    View full-size slide

  56. Communications
    Use Case: “I want to send a user an
    email after they complete an order”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: /send_email

    View full-size slide

  57. Communications
    Use Case: “I want to send a user an
    email after they complete an order”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: /send_email

    View full-size slide

  58. Communications
    Use Case: “I want to send a user an
    email after they cancel an order”
    ➡ Object: Order
    ➡ Event: order:cancelled
    ➡ Webhook: /send_email

    View full-size slide

  59. Communications
    Use Case: “I want to send a user an
    email after their order ships”
    ➡ Object: Shipment
    ➡ Event: shipment:confirm
    ➡ Webhook: /send_email

    View full-size slide

  60. Communications
    Use Case: “I want to send a user a
    SMS after their order ships”
    ➡ Object: Shipment
    ➡ Event: shipment:confirm
    ➡ Webhook: /send_sms

    View full-size slide

  61. Customer Service
    Use Case: “I need to create a support
    ticket for high-risk orders”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: /create_ticket

    View full-size slide

  62. Customer Service
    Use Case: “I need to create a support
    ticket for high-risk orders”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: /create_ticket

    View full-size slide

  63. Customer Service
    Use Case: “I need to create a support
    ticket for high-risk orders”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: /create_ticket

    View full-size slide

  64. Use Cases Summary

    View full-size slide

  65. Use Cases Summary
    ➡ 5 business areas

    View full-size slide

  66. Use Cases Summary
    ➡ 5 business areas
    ➡ 13 use cases

    View full-size slide

  67. Use Cases Summary
    ➡ 5 business areas
    ➡ 13 use cases
    ➡ 15 vendors

    View full-size slide

  68. Use Cases Summary
    ➡ 5 business areas
    ➡ 13 use cases
    ➡ 15 vendors
    All solved with 3 basic ideas:

    View full-size slide

  69. Use Cases Summary
    ➡ 5 business areas
    ➡ 13 use cases
    ➡ 15 vendors
    All solved with 3 basic ideas:
    ➡ Objects

    View full-size slide

  70. Use Cases Summary
    ➡ 5 business areas
    ➡ 13 use cases
    ➡ 15 vendors
    All solved with 3 basic ideas:
    ➡ Objects
    ➡ Events

    View full-size slide

  71. Use Cases Summary
    ➡ 5 business areas
    ➡ 13 use cases
    ➡ 15 vendors
    All solved with 3 basic ideas:
    ➡ Objects
    ➡ Events
    ➡ Webhooks

    View full-size slide

  72. The Hub
    Up close and personal

    View full-size slide

  73. Objects
    It’s all about objects!

    View full-size slide

  74. It’s all about Objects

    View full-size slide

  75. Object Basics
    Object
    ➡ JSON
    ➡ predefined schemas

    View full-size slide

  76. Object Basics
    ➡ Required Attributes
    ➡ id - unique identifier
    Object
    ➡ JSON
    ➡ predefined schemas

    View full-size slide

  77. Object Basics
    ➡ Required Attributes
    ➡ id - unique identifier
    ➡ Suggested Attributes
    ➡ status - objects’ state
    Object
    ➡ JSON
    ➡ predefined schemas

    View full-size slide

  78. Objects
    Orders Products
    Carts Customers

    View full-size slide

  79. Objects
    Orders Products
    Carts Customers
    Inventory
    Payments
    Shipments Returns

    View full-size slide

  80. Order Object
    ➡ email
    ➡ currency
    ➡ totals
    ➡ line_items
    ➡ billing_address
    Order
    ➡ channel
    ➡ placed_on
    ➡ payments
    ➡ shipments
    ➡ shipping_address

    View full-size slide

  81. Order JSON
    {
    "order": {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "line_items": [
    {
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT",
    "external_ref": "",
    Order

    View full-size slide

  82. Objects have schemas
    ➡ Building `The API for commerce`

    View full-size slide

  83. Objects have schemas
    ➡ Building `The API for commerce`
    ➡ Public schema for JSON representation of core
    commerce objects:

    View full-size slide

  84. Objects have schemas
    ➡ Building `The API for commerce`
    ➡ Public schema for JSON representation of core
    commerce objects:
    ➡ orders, products, customers, returns, etc

    View full-size slide

  85. Objects have schemas
    ➡ Building `The API for commerce`
    ➡ Public schema for JSON representation of core
    commerce objects:
    ➡ orders, products, customers, returns, etc

    View full-size slide

  86. Objects have schemas
    ➡ Building `The API for commerce`
    ➡ Public schema for JSON representation of core
    commerce objects:
    ➡ orders, products, customers, returns, etc
    ➡ Hub Official Format

    View full-size slide

  87. Order JSON-Schema
    Order
    {
    "type": "object",
    "required": ["id", "status", "channel"],
    "properties": {
    "id": { "type": "string" },
    "channel": { "type": "string" },
    "currency": { "type": "string" },
    "placed_on": { "type": "string" },
    "status": { "type": "string" },
    "totals": {
    "type": "object",
    "required": ["item","adjustment", "tax", "shipping", "...
    "properties": {

    View full-size slide

  88. https://hubapp.io

    View full-size slide

  89. Need icon + more fake notifications

    View full-size slide

  90. Need icon + more fake notifications

    View full-size slide

  91. Need icon + more fake notifications

    View full-size slide

  92. Extending Objects
    Adding what you need.

    View full-size slide

  93. Custom Order - Simple
    {
    "order": {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "risk_level": "low",
    "line_items": [
    {
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT",
    Order

    View full-size slide

  94. Custom Order - Simple
    {
    "order": {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "risk_level": "low",
    "line_items": [
    {
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT",
    Order

    View full-size slide

  95. Custom Order - Object
    {
    "order": {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "risk": {
    "level": " low",
    "score": 9
    },
    "line_items": [
    Order

    View full-size slide

  96. Custom Order - Object
    {
    "order": {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "risk": {
    "level": " low",
    "score": 9
    },
    "line_items": [
    Order

    View full-size slide

  97. Custom Object
    {
    "pirate": {
    "id": "ARRGH1",
    "status": "angry",
    "name": "Captain Blue Beard",
    "port": "Basse-Terre",
    "ships": [
    {
    "name": "Blue Bell",
    "type": "frigate" }
    ],
    "parrots": [
    {
    Pirate

    View full-size slide

  98. Push API
    Getting objects in

    View full-size slide

  99. Pushing Objects
    ➡ Simple Push API

    View full-size slide

  100. Pushing Objects
    ➡ Simple Push API
    ➡ https://push.hubapp.io

    View full-size slide

  101. Pushing Objects
    ➡ Simple Push API
    ➡ https://push.hubapp.io
    ➡ Secure - unique token per push

    View full-size slide

  102. Pushing Objects
    ➡ Simple Push API
    ➡ https://push.hubapp.io
    ➡ Secure - unique token per push
    ➡ Fast - validated asynchronously

    View full-size slide

  103. Pushing Objects
    ➡ Simple Push API
    ➡ https://push.hubapp.io
    ➡ Secure - unique token per push
    ➡ Fast - validated asynchronously
    ➡ Customizable - attributes & objects

    View full-size slide

  104. Pushing Objects
    ➡ Simple Push API
    ➡ https://push.hubapp.io
    ➡ Secure - unique token per push
    ➡ Fast - validated asynchronously
    ➡ Customizable - attributes & objects
    ➡ Partial updates

    View full-size slide

  105. Pushing Objects
    ➡ Simple Push API
    ➡ https://push.hubapp.io
    ➡ Secure - unique token per push
    ➡ Fast - validated asynchronously
    ➡ Customizable - attributes & objects
    ➡ Partial updates

    View full-size slide

  106. Pushing Objects
    ➡ Simple Push API
    ➡ https://push.hubapp.io
    ➡ Secure - unique token per push
    ➡ Fast - validated asynchronously
    ➡ Customizable - attributes & objects
    ➡ Partial updates

    View full-size slide

  107. Pushing Objects
    ➡ Simple Push API
    ➡ https://push.hubapp.io
    ➡ Secure - unique token per push
    ➡ Fast - validated asynchronously
    ➡ Customizable - attributes & objects
    ➡ Partial updates

    View full-size slide

  108. Pushing Objects
    ➡ Simple Push API
    ➡ https://push.hubapp.io
    ➡ Secure - unique token per push
    ➡ Fast - validated asynchronously
    ➡ Customizable - attributes & objects
    ➡ Partial updates

    View full-size slide

  109. Push Example - Single
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    ...
    }
    ]
    }
    POST

    View full-size slide

  110. Push Example - Single
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    ...
    }
    ]
    }
    POST
    {
    "orders": 1
    }
    Response: 202

    View full-size slide

  111. Push Example - Multiple
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD" } ,
    {
    "id": "R154085347",
    "status": "complete",
    "channel": "spree",
    POST

    View full-size slide

  112. Push Example - Multiple
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD" } ,
    {
    "id": "R154085347",
    "status": "complete",
    "channel": "spree",
    POST
    {
    "orders": 2
    }
    Response: 202

    View full-size slide

  113. Push Example - Objects
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD" } ]
    "products": [
    {
    "id": "ROR-123",
    "name": "Ruby on Rails Tote",
    POST

    View full-size slide

  114. Push Example - Objects
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD" } ]
    "products": [
    {
    "id": "ROR-123",
    "name": "Ruby on Rails Tote",
    POST
    {
    "orders": 1,
    "products": 1
    }
    Response: 202

    View full-size slide

  115. Push Example - Custom Object
    {
    "pirates": [
    {
    "id": "ARRGH1",
    "status": "angry",
    "name": "Captain Blue Beard",
    "email": "[email protected]",
    "ships": [
    {
    "name": "Blue Bell",
    "type": "frigate" }
    ]
    POST

    View full-size slide

  116. Push Example - Custom Object
    {
    "pirates": [
    {
    "id": "ARRGH1",
    "status": "angry",
    "name": "Captain Blue Beard",
    "email": "[email protected]",
    "ships": [
    {
    "name": "Blue Bell",
    "type": "frigate" }
    ]
    POST
    {
    "pirates": 1
    }
    Response: 202

    View full-size slide

  117. Push Example - Partial
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "currency": "USD",
    ...
    }
    ]
    }
    Push 1

    View full-size slide

  118. Push Example - Partial
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "currency": "USD",
    ...
    }
    ]
    }
    Push 1
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "cancelled"
    }
    ]
    }
    Push 2

    View full-size slide

  119. Push Example - Partial
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "currency": "USD",
    ...
    }
    ]
    }
    Push 1
    {
    "orders": [
    {
    "id": "R154085346",
    "status": "cancelled"
    }
    ]
    }
    Push 2
    {
    "order":
    {
    "id": "R154085346",
    "status": "cancelled",
    "channel": "spree",
    "currency": "USD"
    ...
    }
    }
    Result

    View full-size slide

  120. Authentication
    ➡ HMAC

    View full-size slide

  121. Authentication
    ➡ HMAC
    ➡ json + secret key + timestamp (UTC)

    View full-size slide

  122. Authentication
    ➡ HMAC
    ➡ json + secret key + timestamp (UTC)
    ➡ Each push is only valid for:

    View full-size slide

  123. Authentication
    ➡ HMAC
    ➡ json + secret key + timestamp (UTC)
    ➡ Each push is only valid for:
    ➡ the exact body you intended

    View full-size slide

  124. Authentication
    ➡ HMAC
    ➡ json + secret key + timestamp (UTC)
    ➡ Each push is only valid for:
    ➡ the exact body you intended
    ➡ a small time window

    View full-size slide

  125. Authentication
    STORE_ID = "5180e2507575e48dd0000001"
    SECRET_KEY = "1234567890"
    timestamp = Time.now.utc().to_i
    json_payload = JSON.generate({ orders: [ { id: 'R1234', status: 'cancelled' } ] })
    data = "#{json_payload}@#{timestamp}"
    SHA1 = OpenSSL::Digest::Digest.new('sha1')
    hmac = OpenSSL::HMAC.hexdigest(SHA1, SECRET_KEY, data)
    response = HTTParty.post(‘https://hub.spreecommerce.com/push’, {
    body: json_payload,
    headers: {
    'X-Hub-Store' => STORE_ID,
    'X-Hub-Token' => hmac,
    'X-Hub-Timestamp' => timestamp.to_s,
    'CONTENT_TYPE' => 'application/json',
    'ACCEPT' => 'application/json' }
    })

    View full-size slide

  126. Authentication
    STORE_ID = "5180e2507575e48dd0000001"
    SECRET_KEY = "1234567890"
    timestamp = Time.now.utc().to_i
    json_payload = JSON.generate({ orders: [ { id: 'R1234', status: 'cancelled' } ] })
    data = "#{json_payload}@#{timestamp}"
    SHA1 = OpenSSL::Digest::Digest.new('sha1')
    hmac = OpenSSL::HMAC.hexdigest(SHA1, SECRET_KEY, data)
    response = HTTParty.post(‘https://hub.spreecommerce.com/push’, {
    body: json_payload,
    headers: {
    'X-Hub-Store' => STORE_ID,
    'X-Hub-Token' => hmac,
    'X-Hub-Timestamp' => timestamp.to_s,
    'CONTENT_TYPE' => 'application/json',
    'ACCEPT' => 'application/json' }
    })

    View full-size slide

  127. Authentication
    STORE_ID = "5180e2507575e48dd0000001"
    SECRET_KEY = "1234567890"
    timestamp = Time.now.utc().to_i
    json_payload = JSON.generate({ orders: [ { id: 'R1234', status: 'cancelled' } ] })
    data = "#{json_payload}@#{timestamp}"
    SHA1 = OpenSSL::Digest::Digest.new('sha1')
    hmac = OpenSSL::HMAC.hexdigest(SHA1, SECRET_KEY, data)
    response = HTTParty.post(‘https://hub.spreecommerce.com/push’, {
    body: json_payload,
    headers: {
    'X-Hub-Store' => STORE_ID,
    'X-Hub-Token' => hmac,
    'X-Hub-Timestamp' => timestamp.to_s,
    'CONTENT_TYPE' => 'application/json',
    'ACCEPT' => 'application/json' }
    })

    View full-size slide

  128. Authentication
    STORE_ID = "5180e2507575e48dd0000001"
    SECRET_KEY = "1234567890"
    timestamp = Time.now.utc().to_i
    json_payload = JSON.generate({ orders: [ { id: 'R1234', status: 'cancelled' } ] })
    data = "#{json_payload}@#{timestamp}"
    SHA1 = OpenSSL::Digest::Digest.new('sha1')
    hmac = OpenSSL::HMAC.hexdigest(SHA1, SECRET_KEY, data)
    response = HTTParty.post(‘https://hub.spreecommerce.com/push’, {
    body: json_payload,
    headers: {
    'X-Hub-Store' => STORE_ID,
    'X-Hub-Token' => hmac,
    'X-Hub-Timestamp' => timestamp.to_s,
    'CONTENT_TYPE' => 'application/json',
    'ACCEPT' => 'application/json' }
    })

    View full-size slide

  129. Authentication
    STORE_ID = "5180e2507575e48dd0000001"
    SECRET_KEY = "1234567890"
    timestamp = Time.now.utc().to_i
    json_payload = JSON.generate({ orders: [ { id: 'R1234', status: 'cancelled' } ] })
    data = "#{json_payload}@#{timestamp}"
    SHA1 = OpenSSL::Digest::Digest.new('sha1')
    hmac = OpenSSL::HMAC.hexdigest(SHA1, SECRET_KEY, data)
    response = HTTParty.post(‘https://hub.spreecommerce.com/push’, {
    body: json_payload,
    headers: {
    'X-Hub-Store' => STORE_ID,
    'X-Hub-Token' => hmac,
    'X-Hub-Timestamp' => timestamp.to_s,
    'CONTENT_TYPE' => 'application/json',
    'ACCEPT' => 'application/json' }
    })

    View full-size slide

  130. Push support for Spree
    ➡ Built-in to Spree 2.3

    View full-size slide

  131. Push support for Spree
    ➡ Built-in to Spree 2.3
    ➡ A new ‘core’ gem

    View full-size slide

  132. Push support for Spree
    ➡ Built-in to Spree 2.3
    ➡ A new ‘core’ gem
    ➡ Disabled by default, activated via preference.

    View full-size slide

  133. Push support for Spree
    ➡ Built-in to Spree 2.3
    ➡ A new ‘core’ gem
    ➡ Disabled by default, activated via preference.
    ➡ Will include both Push and Webhooks*

    View full-size slide

  134. Push support for Spree
    ➡ Built-in to Spree 2.3
    ➡ A new ‘core’ gem
    ➡ Disabled by default, activated via preference.
    ➡ Will include both Push and Webhooks*
    ➡ Back-porting all the way to 1.3.x

    View full-size slide

  135. High Availability
    In push, we trust.

    View full-size slide

  136. Events
    An interesting change to an object.

    View full-size slide

  137. Events
    “Tom completed an order for 3 t-shirts”

    View full-size slide

  138. Events
    “Tom completed an order for 3 t-shirts”
    “Tom’s order has been dispatched”

    View full-size slide

  139. Events
    “Tom completed an order for 3 t-shirts”
    “Tom’s order has been dispatched”
    “A new user called Jane registered”

    View full-size slide

  140. Events
    “Tom completed an order for 3 t-shirts”
    “Tom’s order has been dispatched”
    “A new user called Jane registered”
    “Jane added a hat to her cart”

    View full-size slide

  141. Events
    “Tom completed an order for 3 t-shirts”
    “Tom’s order has been dispatched”
    “A new user called Jane registered”
    “Jane added a hat to her cart”
    “Jane’s cart has be inactive for 5 days”

    View full-size slide

  142. Events
    “Tom completed an order for 3 t-shirts”
    “Tom’s order has been dispatched”
    “A new user called Jane registered”
    “Jane added a hat to her cart”
    “Jane’s cart has be inactive for 5 days”
    “We launched a new Tote product”

    View full-size slide

  143. Events
    “Tom completed an order for 3 t-shirts”
    “Tom’s order has been dispatched”
    “A new user called Jane registered”
    “Jane added a hat to her cart”
    “Jane’s cart has be inactive for 5 days”
    “We launched a new Tote product”
    “There’s 200 Totes in stock in NYC”

    View full-size slide

  144. Events
    ➡ Created as objects change

    View full-size slide

  145. Events
    ➡ Created as objects change
    ➡ Two different ways they can be generated:

    View full-size slide

  146. Events
    ➡ Created as objects change
    ➡ Two different ways they can be generated:
    ➡ created vs. updated - automatic

    View full-size slide

  147. Events
    ➡ Created as objects change
    ➡ Two different ways they can be generated:
    ➡ created vs. updated - automatic
    ➡ event builder - custom

    View full-size slide

  148. Events
    ➡ Created as objects change
    ➡ Two different ways they can be generated:
    ➡ created vs. updated - automatic
    ➡ event builder - custom
    ➡ Connect objects to webhooks*

    View full-size slide

  149. Events: Create Vs. Update

    View full-size slide

  150. Events: Create Vs. Update
    ➡ The first time the hub receives an object:
    ➡ object:created
    ➡ order:created, inventory:created, pirate:created

    View full-size slide

  151. Events: Create Vs. Update
    ➡ The first time the hub receives an object:
    ➡ object:created
    ➡ order:created, inventory:created, pirate:created
    ➡ Each time a real change is received:
    ➡ object:updated
    ➡ product:updated, customer:updated

    View full-size slide

  152. Events: Builder UI

    View full-size slide

  153. Events: Builder UI
    Easily create powerful event queries using the builder UI.

    View full-size slide

  154. Events trigger webhooks

    View full-size slide

  155. Events trigger webhooks
    How objects flow through the hub.

    View full-size slide

  156. Events & Webhooks
    Event Webhook Integration

    View full-size slide

  157. Events & Webhooks
    Event Webhook Integration
    shipment:ready /add_shipment

    View full-size slide

  158. Events & Webhooks
    Event Webhook Integration
    shipment:ready /add_shipment
    order:created /add_order

    View full-size slide

  159. Events & Webhooks
    Event Webhook Integration
    shipment:ready /add_shipment
    order:created /add_order
    shipment:confirm /update_shipment

    View full-size slide

  160. Evented Processing
    ➡ Decouples processing from the store front.
    ➡ Enables scability.

    View full-size slide

  161. Webhooks
    Getting objects out

    View full-size slide

  162. Webhooks - Goals

    View full-size slide

  163. Webhooks - Goals
    ➡ A set of common patterns for processing events

    View full-size slide

  164. Webhooks - Goals
    ➡ A set of common patterns for processing events
    ➡ Well documented

    View full-size slide

  165. Webhooks - Goals
    ➡ A set of common patterns for processing events
    ➡ Well documented
    ➡ Easy to test

    View full-size slide

  166. Webhooks - Goals
    ➡ A set of common patterns for processing events
    ➡ Well documented
    ➡ Easy to test

    View full-size slide

  167. Webhooks - Goals
    ➡ A set of common patterns for processing events
    ➡ Well documented
    ➡ Easy to test
    ➡ Make integration development even easier

    View full-size slide

  168. Webhooks - Objects

    View full-size slide

  169. Webhooks - Objects
    Standard naming convention for webhooks:

    View full-size slide

  170. Webhooks - Objects
    Standard naming convention for webhooks:
    ➡ add - alerts target system of new object

    View full-size slide

  171. Webhooks - Objects
    Standard naming convention for webhooks:
    ➡ add - alerts target system of new object
    ➡ update - sends object changes

    View full-size slide

  172. Webhooks - Objects
    Standard naming convention for webhooks:
    ➡ add - alerts target system of new object
    ➡ update - sends object changes
    ➡ get - fetch object changes

    View full-size slide

  173. Webhooks - Objects

    View full-size slide

  174. Webhooks - Objects
    ➡ add_order
    ➡ update_order
    ➡ get_orders
    Order

    View full-size slide

  175. Webhooks - Objects
    ➡ add_order
    ➡ update_order
    ➡ get_orders
    Order
    ➡ add_product
    ➡ update_product
    ➡ get_products
    Product

    View full-size slide

  176. Webhooks - Objects
    ➡ add_order
    ➡ update_order
    ➡ get_orders
    Order
    ➡ add_product
    ➡ update_product
    ➡ get_products
    Product
    ➡ add_customer
    ➡ update_customer
    ➡ get_customers
    Customer

    View full-size slide

  177. Webhooks - Objects
    ➡ add_order
    ➡ update_order
    ➡ get_orders
    Order
    ➡ add_product
    ➡ update_product
    ➡ get_products
    Product
    ➡ add_customer
    ➡ update_customer
    ➡ get_customers
    Customer
    ➡ add_pirate
    ➡ update_pirate
    ➡ get_pirates
    Pirate

    View full-size slide

  178. Webhooks - Other

    View full-size slide

  179. Webhooks - Other
    ➡ get_inventory
    ➡ set_inventory
    Inventory

    View full-size slide

  180. Webhooks - Other
    ➡ get_inventory
    ➡ set_inventory
    Inventory
    ➡ send_email
    ➡ send_sms
    ➡ create_ticket
    ➡ add_to_list
    Comms.

    View full-size slide

  181. Implementing Webhooks
    ➡ An application capable of receiving a HTTP POST
    request.

    View full-size slide

  182. Implementing Webhooks
    ➡ An application capable of receiving a HTTP POST
    request.
    ➡ Can use any language or framework.

    View full-size slide

  183. Implementing Webhooks
    ➡ An application capable of receiving a HTTP POST
    request.
    ➡ Can use any language or framework.
    ➡ Can be standalone, or just a route on a larger app.

    View full-size slide

  184. Implementing Webhooks
    ➡ An application capable of receiving a HTTP POST
    request.
    ➡ Can use any language or framework.
    ➡ Can be standalone, or just a route on a larger app.
    ➡ Needs to respond with JSON.

    View full-size slide

  185. Implementing Webhooks
    ➡ An application capable of receiving a HTTP POST
    request.
    ➡ Can use any language or framework.
    ➡ Can be standalone, or just a route on a larger app.
    ➡ Needs to respond with JSON.
    ➡ Can be hosted anywhere.

    View full-size slide

  186. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST

    View full-size slide

  187. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST

    View full-size slide

  188. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST

    View full-size slide

  189. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Order imported to Quickbooks",
    }

    View full-size slide

  190. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Order imported to Quickbooks",
    }

    View full-size slide

  191. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Order imported to Quickbooks",
    }

    View full-size slide

  192. /update_product Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "product": {
    "id": 58,
    "parent_id": 11,
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT-S",
    "description": "Awesome Spree T-Shirt",
    "price": 35.0,
    "cost_price": 22.33,
    "available_on": "2014-01-29T14:01:28.000Z",
    "permalink": "spree-tshirt",
    "meta_description": null,
    "meta_keywords": null,
    "shipping_category": "Default",
    POST

    View full-size slide

  193. /update_product Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "product": {
    "id": 58,
    "parent_id": 11,
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT-S",
    "description": "Awesome Spree T-Shirt",
    "price": 35.0,
    "cost_price": 22.33,
    "available_on": "2014-01-29T14:01:28.000Z",
    "permalink": "spree-tshirt",
    "meta_description": null,
    "meta_keywords": null,
    "shipping_category": "Default",
    POST

    View full-size slide

  194. /update_product Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "product": {
    "id": 58,
    "parent_id": 11,
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT-S",
    "description": "Awesome Spree T-Shirt",
    "price": 35.0,
    "cost_price": 22.33,
    "available_on": "2014-01-29T14:01:28.000Z",
    "permalink": "spree-tshirt",
    "meta_description": null,
    "meta_keywords": null,
    "shipping_category": "Default",
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Product updated in NetSuite",
    }

    View full-size slide

  195. /update_product Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "product": {
    "id": 58,
    "parent_id": 11,
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT-S",
    "description": "Awesome Spree T-Shirt",
    "price": 35.0,
    "cost_price": 22.33,
    "available_on": "2014-01-29T14:01:28.000Z",
    "permalink": "spree-tshirt",
    "meta_description": null,
    "meta_keywords": null,
    "shipping_category": "Default",
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Product updated in NetSuite",
    }

    View full-size slide

  196. /update_product Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "product": {
    "id": 58,
    "parent_id": 11,
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT-S",
    "description": "Awesome Spree T-Shirt",
    "price": 35.0,
    "cost_price": 22.33,
    "available_on": "2014-01-29T14:01:28.000Z",
    "permalink": "spree-tshirt",
    "meta_description": null,
    "meta_keywords": null,
    "shipping_category": "Default",
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Product updated in NetSuite",
    }

    View full-size slide

  197. Webhook: Parameters
    {
    "request_id": "52f367367575e449c3000001",
    "order": {},
    "parameters": {
    "quickbooks.access_token": "qyprd1vQF...",
    "quickbooks.access_secret": "xTrT6dckbY...",
    "quickbooks.realm": "1231232"
    }
    }
    POST

    View full-size slide

  198. Webhook: Parameters
    {
    "request_id": "52f367367575e449c3000001",
    "order": {},
    "parameters": {
    "quickbooks.access_token": "qyprd1vQF...",
    "quickbooks.access_secret": "xTrT6dckbY...",
    "quickbooks.realm": "1231232"
    }
    }
    POST

    View full-size slide

  199. Webhook: Parameters
    {
    "request_id": "52f367367575e449c3000001",
    "order": {},
    "parameters": {
    "quickbooks.access_token": "qyprd1vQF...",
    "quickbooks.access_secret": "xTrT6dckbY...",
    "quickbooks.realm": "1231232"
    }
    }
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Order imported to Quickbooks",
    }

    View full-size slide

  200. /get_customers Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "parameters": {
    "since": "2014-02-03T17:29:00.000Z
    }
    }
    POST

    View full-size slide

  201. /get_customers Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "parameters": {
    "since": "2014-02-03T17:29:00.000Z
    }
    }
    POST

    View full-size slide

  202. /get_customers Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "parameters": {
    "since": "2014-02-03T17:29:00.000Z
    }
    }
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "customers": [
    { "id": 1,
    "email": "[email protected]" },
    { "id": 2,
    "first_name": "Sean",
    "last_name": "Schofield"
    ],
    "parameters": {
    "since": "2014-02-03T17:35:00.000Z"
    }
    }

    View full-size slide

  203. /get_customers Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "parameters": {
    "since": "2014-02-03T17:29:00.000Z
    }
    }
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "customers": [
    { "id": 1,
    "email": "[email protected]" },
    { "id": 2,
    "first_name": "Sean",
    "last_name": "Schofield"
    ],
    "parameters": {
    "since": "2014-02-03T17:35:00.000Z"
    }
    }

    View full-size slide

  204. /get_customers Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "parameters": {
    "since": "2014-02-03T17:29:00.000Z
    }
    }
    POST Response: 200 OK
    {
    "request_id": "52f367367575e449c3000001",
    "customers": [
    { "id": 1,
    "email": "[email protected]" },
    { "id": 2,
    "first_name": "Sean",
    "last_name": "Schofield"
    ],
    "parameters": {
    "since": "2014-02-03T17:35:00.000Z"
    }
    }

    View full-size slide

  205. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST

    View full-size slide

  206. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST Response: 500 Error
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Order import failed for NetSuite",
    }

    View full-size slide

  207. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST Response: 500 Error
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Order import failed for NetSuite",
    }

    View full-size slide

  208. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST Response: 500 Error
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "Order import failed for NetSuite",
    }

    View full-size slide

  209. Authentication
    ➡ With each webhook, the hub includes the
    following headers:

    View full-size slide

  210. Authentication
    ➡ With each webhook, the hub includes the
    following headers:
    ➡ X-Hub-Store
    ➡ Unique store identifier

    View full-size slide

  211. Authentication
    ➡ With each webhook, the hub includes the
    following headers:
    ➡ X-Hub-Store
    ➡ Unique store identifier
    ➡ X-Hub-Token
    ➡ Pre-shared secret to identify the hub is sending
    the POST.

    View full-size slide

  212. endpoint_base
    https://github.com/spree/endpoint_base

    View full-size slide

  213. endpoint_base
    ➡ Supports both Sinatra & Rails endpoints, & provides:

    View full-size slide

  214. endpoint_base
    ➡ Supports both Sinatra & Rails endpoints, & provides:
    ➡ Authentication

    View full-size slide

  215. endpoint_base
    ➡ Supports both Sinatra & Rails endpoints, & provides:
    ➡ Authentication
    ➡ Helper methods & variables

    View full-size slide

  216. endpoint_base
    ➡ Supports both Sinatra & Rails endpoints, & provides:
    ➡ Authentication
    ➡ Helper methods & variables
    ➡ @payload & @config

    View full-size slide

  217. endpoint_base
    ➡ Supports both Sinatra & Rails endpoints, & provides:
    ➡ Authentication
    ➡ Helper methods & variables
    ➡ @payload & @config
    ➡ Response DSL

    View full-size slide

  218. endpoint_base
    ➡ Supports both Sinatra & Rails endpoints, & provides:
    ➡ Authentication
    ➡ Helper methods & variables
    ➡ @payload & @config
    ➡ Response DSL
    ➡ Rendering

    View full-size slide

  219. The Hub in Action

    View full-size slide

  220. Handling Failures
    Retry Patterns

    View full-size slide

  221. Retry Patterns: Failure
    hub

    View full-size slide

  222. Retry Patterns: Failure
    hub
    Order PUSH
    order:created

    View full-size slide

  223. Retry Patterns: Failure
    hub Netsuite
    Order PUSH
    order:created

    View full-size slide

  224. Retry Patterns: Failure
    hub
    /add_order
    500: fail!
    Netsuite
    Order PUSH
    order:created

    View full-size slide

  225. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST

    View full-size slide

  226. /add_order Webhook
    {
    "request_id": "52f367367575e449c3000001",
    "order": {
    "id": "R154085346",
    "number": "R154085346",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "updated_at": "2014-02-03T17:29:15.253Z",
    "status": "complete",
    "totals": {
    "item": 100.0,
    "adjustment": 5.0,
    "tax": 0.0,
    POST Response: 500 Error
    {
    "request_id": "52f367367575e449c3000001",
    "summary": "SKU ‘ROR-123’ is invalid.",
    }

    View full-size slide

  227. Retry Patterns: Back-off
    hub Netsuite
    Order PUSH
    order:created

    View full-size slide

  228. Retry Patterns: Back-off
    hub
    /add_order
    500: fail!
    /add_order
    200: success
    Netsuite
    /add_order
    500: fail!
    /add_order
    500: fail!
    Order PUSH
    order:created

    View full-size slide

  229. Retry Patterns: Back-off
    hub
    /add_order
    500: fail!
    /add_order
    200: success
    Netsuite
    /add_order
    500: fail!
    /add_order
    500: fail!
    Order PUSH

    View full-size slide

  230. Retry Patterns: Auto-recovery
    hub Netsuite
    Order PUSH
    order:created

    View full-size slide

  231. Retry Patterns: Auto-recovery
    hub Netsuite
    Order PUSH
    order:created
    500: fail!
    /add_order
    500: fail!
    /add_order

    View full-size slide

  232. Retry Patterns: Auto-recovery
    hub Netsuite
    Order PUSH
    order:created
    Order PUSH
    500: fail!
    /add_order
    500: fail!
    /add_order
    *

    View full-size slide

  233. Retry Patterns: Auto-recovery
    hub
    /add_order
    Netsuite
    Order PUSH
    order:created
    Order PUSH
    200: success
    500: fail!
    /add_order
    500: fail!
    /add_order
    *

    View full-size slide

  234. Retry Patterns: No retry
    hub Netsuite
    Order PUSH
    order:created

    View full-size slide

  235. Retry Patterns: No retry
    hub
    order:created
    500: fail!
    Netsuite
    Order PUSH
    order:created

    View full-size slide

  236. Macros
    Object converters

    View full-size slide

  237. Macros
    ➡ Embedded javascript functions

    View full-size slide

  238. Macros
    ➡ Embedded javascript functions
    ➡ Get applied to the body of a webhook

    View full-size slide

  239. Macros
    ➡ Embedded javascript functions
    ➡ Get applied to the body of a webhook
    ➡ before it’s sent to the integration

    View full-size slide

  240. Macros
    ➡ Embedded javascript functions
    ➡ Get applied to the body of a webhook
    ➡ before it’s sent to the integration
    ➡ Has access to:

    View full-size slide

  241. Macros
    ➡ Embedded javascript functions
    ➡ Get applied to the body of a webhook
    ➡ before it’s sent to the integration
    ➡ Has access to:
    ➡ entire body (including parameters).

    View full-size slide

  242. Macros
    ➡ Embedded javascript functions
    ➡ Get applied to the body of a webhook
    ➡ before it’s sent to the integration
    ➡ Has access to:
    ➡ entire body (including parameters).
    ➡ underscore.js, helper methods, more.

    View full-size slide

  243. Communications
    Use Case: “I want to send a user an
    email after they complete an order”
    ➡ Object: Order
    ➡ Event: order:created
    ➡ Webhook: POST /send_email

    View full-size slide

  244. Marcos - Use Case
    {
    "order": {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "totals": { },
    "line_items": [
    {
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT",
    "external_ref": "",
    "quantity": 2,
    Order Object

    View full-size slide

  245. Marcos - Use Case
    {
    "order": {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "totals": { },
    "line_items": [
    {
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT",
    "external_ref": "",
    "quantity": 2,
    Order Object
    {
    "request_id": "52f367367575e449c3000001",
    "email": {
    "to": "[email protected]",
    "from": "[email protected]",
    "subject": "Order R123456 was shipped!",
    "template": "order_shipped",
    "variables": {
    "customer_name": "John Smith",
    "order_total": "100.00",
    "order_tracking": "XYZ123"
    }
    }
    }
    /send_email webhook

    View full-size slide

  246. Marcos - Use Case
    {
    "order": {
    "id": "R154085346",
    "status": "complete",
    "channel": "spree",
    "email": "[email protected]",
    "currency": "USD",
    "placed_on": "2014-02-03T17:29:15.219Z",
    "totals": { },
    "line_items": [
    {
    "name": "Spree T-Shirt",
    "sku": "SPREE-T-SHIRT",
    "external_ref": "",
    "quantity": 2,
    Order Object
    {
    "request_id": "52f367367575e449c3000001",
    "email": {
    "to": "[email protected]",
    "from": "[email protected]",
    "subject": "Order R123456 was shipped!",
    "template": "order_shipped",
    "variables": {
    "customer_name": "John Smith",
    "order_total": "100.00",
    "order_tracking": "XYZ123"
    }
    }
    }
    /send_email webhook

    View full-size slide

  247. Macro - Example
    var email = { variables: {} };
    email.to = request.order.email;
    email.from = find_param('mandrill.order_confirimation_from');
    email.template = find_param('mandrill.order_confirimation_template');
    email.variables.order_number = request.order.number;
    email.variables.shipping_first_name = request.order.shipping_address.firstname;
    email.variables.shipping_last_name = request.order.shipping_address.lastname;
    email.variables.total = Number(payload.order.totals.order).toFixed(2);
    _.each(request.order.line_items, function(item){
    email.variables.line_item_rows += '' + item.name + '\
    ' + item.quantity + '\
    $' + Number(item.price).toFixed(2) + '';
    });
    request.email = email;

    View full-size slide

  248. Macro - Example
    var email = { variables: {} };
    email.to = request.order.email;
    email.from = find_param('mandrill.order_confirimation_from');
    email.template = find_param('mandrill.order_confirimation_template');
    email.variables.order_number = request.order.number;
    email.variables.shipping_first_name = request.order.shipping_address.firstname;
    email.variables.shipping_last_name = request.order.shipping_address.lastname;
    email.variables.total = Number(payload.order.totals.order).toFixed(2);
    _.each(request.order.line_items, function(item){
    email.variables.line_item_rows += '' + item.name + '\
    ' + item.quantity + '\
    $' + Number(item.price).toFixed(2) + '';
    });
    request.email = email;

    View full-size slide

  249. Macro - Example
    var email = { variables: {} };
    email.to = request.order.email;
    email.from = find_param('mandrill.order_confirimation_from');
    email.template = find_param('mandrill.order_confirimation_template');
    email.variables.order_number = request.order.number;
    email.variables.shipping_first_name = request.order.shipping_address.firstname;
    email.variables.shipping_last_name = request.order.shipping_address.lastname;
    email.variables.total = Number(payload.order.totals.order).toFixed(2);
    _.each(request.order.line_items, function(item){
    email.variables.line_item_rows += '' + item.name + '\
    ' + item.quantity + '\
    $' + Number(item.price).toFixed(2) + '';
    });
    request.email = email;

    View full-size slide

  250. Macro - Example
    var email = { variables: {} };
    email.to = request.order.email;
    email.from = find_param('mandrill.order_confirimation_from');
    email.template = find_param('mandrill.order_confirimation_template');
    email.variables.order_number = request.order.number;
    email.variables.shipping_first_name = request.order.shipping_address.firstname;
    email.variables.shipping_last_name = request.order.shipping_address.lastname;
    email.variables.total = Number(payload.order.totals.order).toFixed(2);
    _.each(request.order.line_items, function(item){
    email.variables.line_item_rows += '' + item.name + '\
    ' + item.quantity + '\
    $' + Number(item.price).toFixed(2) + '';
    });
    request.email = email;

    View full-size slide

  251. Macro - Example
    var email = { variables: {} };
    email.to = request.order.email;
    email.from = find_param('mandrill.order_confirimation_from');
    email.template = find_param('mandrill.order_confirimation_template');
    email.variables.order_number = request.order.number;
    email.variables.shipping_first_name = request.order.shipping_address.firstname;
    email.variables.shipping_last_name = request.order.shipping_address.lastname;
    email.variables.total = Number(payload.order.totals.order).toFixed(2);
    _.each(request.order.line_items, function(item){
    email.variables.line_item_rows += '' + item.name + '\
    ' + item.quantity + '\
    $' + Number(item.price).toFixed(2) + '';
    });
    request.email = email;

    View full-size slide

  252. Macro - Example
    var email = { variables: {} };
    email.to = request.order.email;
    email.from = find_param('mandrill.order_confirimation_from');
    email.template = find_param('mandrill.order_confirimation_template');
    email.variables.order_number = request.order.number;
    email.variables.shipping_first_name = request.order.shipping_address.firstname;
    email.variables.shipping_last_name = request.order.shipping_address.lastname;
    email.variables.total = Number(payload.order.totals.order).toFixed(2);
    _.each(request.order.line_items, function(item){
    email.variables.line_item_rows += '' + item.name + '\
    ' + item.quantity + '\
    $' + Number(item.price).toFixed(2) + '';
    });
    request.email = email;

    View full-size slide

  253. spreecommerce.com/docs/hub/
    Hub Guides & Test tool

    View full-size slide

  254. Learn More: Talk to us!
    Sean, Josh, Brad, Mike & Andrew

    View full-size slide

  255. SpreeConf: Whisky

    View full-size slide

  256. SpreeConf: Whisky
    http://www.whiskyblender.com/b.php?code=WB35828

    View full-size slide

  257. Questions?
    Answers?

    View full-size slide