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. A peace of mind that everything is happening as it

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

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

    should. OR WE’LL COME HELP YOU!
  4. What problems does it solve? The Acme Widgets Store ➡

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

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

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

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

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

    Third Party Logistics ➡ Accounting Application ➡ Multi-channel Sales ➡ Customer Service ➡ Analytics & Marketing And much more!
  10. History - Timeline ➡ Version 1 went into production late

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

    2012 ➡ Version 2..4 deployed up to Q1 2013 ➡ Version 5 - SpreeConf DC - May 2013
  12. 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’
  13. 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
  14. 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’
  15. History - Integrations ➡ NetSuite ➡ OpenERP ➡ QuickBooks ➡

    Quiet Logistics ➡ Shipwire ➡ DotCom ➡ PCH International ➡ Fosdick ➡ VPD ➡ RLM ➡ Simparel ➡ Twilio ➡ Zendesk ➡ Desk ➡ Tender ➡ Mandrill ➡ Exact Target ➡ SMTP
  16. Three core terms Object ➡ A JSON representation of an

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

    order, product, customer, shipment, return, etc. Event ➡ An interesting change to an object.
  18. 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.
  19. Common Use Cases ➡ Third Party Logistics ➡ Accounting &

    ERP ➡ Multi channel sales ➡ Communications ➡ Customer Service
  20. 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
  21. 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
  22. 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
  23. Third Party Logistics Use Case: “I want to update my

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

    store with my 3PL’s stock levels.” ➡ Object: Inventory ➡ Event: schedule ➡ Webhook: /get_inventory
  25. 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
  26. 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
  27. 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
  28. 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.”
  29. Accounting & ERP Use Case: “I need to import Products

    from my ERP to my store front.” ➡ Object: Product ➡ Event: schedule ➡ Webhook: /get_products
  30. 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
  31. 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
  32. 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
  33. Communications Use Case: “I want to send a user an

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

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

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

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

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

    SMS after their order ships” ➡ Object: Shipment ➡ Event: shipment:confirm ➡ Webhook: /send_sms
  39. Customer Service Use Case: “I need to create a support

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

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

    ticket for high-risk orders” ➡ Object: Order ➡ Event: order:created ➡ Webhook: /create_ticket
  42. Use Cases Summary ➡ 5 business areas ➡ 13 use

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

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

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

    cases ➡ 15 vendors All solved with 3 basic ideas: ➡ Objects ➡ Events ➡ Webhooks
  46. Object Basics ➡ Required Attributes ➡ id - unique identifier

    ➡ Suggested Attributes ➡ status - objects’ state Object ➡ JSON ➡ predefined schemas
  47. Order Object ➡ email ➡ currency ➡ totals ➡ line_items

    ➡ billing_address Order ➡ channel ➡ placed_on ➡ payments ➡ shipments ➡ shipping_address
  48. 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
  49. Objects have schemas ➡ Building `The API for commerce` ➡

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

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

    Public schema for JSON representation of core commerce objects: ➡ orders, products, customers, returns, etc
  52. 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
  53. 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": {
  54. 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
  55. 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
  56. 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
  57. 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
  58. Custom Object { "pirate": { "id": "ARRGH1", "status": "angry", "name":

    "Captain Blue Beard", "port": "Basse-Terre", "ships": [ { "name": "Blue Bell", "type": "frigate" } ], "parrots": [ { Pirate
  59. Pushing Objects ➡ Simple Push API ➡ https://push.hubapp.io ➡ Secure

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

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

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

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

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

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

    - unique token per push ➡ Fast - validated asynchronously ➡ Customizable - attributes & objects ➡ Partial updates
  66. Push Example - Single { "orders": [ { "id": "R154085346",

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

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

    "status": "complete", "channel": "spree", "email": "[email protected]", "currency": "USD" } , { "id": "R154085347", "status": "complete", "channel": "spree", POST
  69. 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
  70. 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
  71. 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
  72. Push Example - Custom Object { "pirates": [ { "id":

    "ARRGH1", "status": "angry", "name": "Captain Blue Beard", "email": "[email protected]", "ships": [ { "name": "Blue Bell", "type": "frigate" } ] POST
  73. 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
  74. Push Example - Partial { "orders": [ { "id": "R154085346",

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

    "status": "complete", "channel": "spree", "currency": "USD", ... } ] } Push 1 { "orders": [ { "id": "R154085346", "status": "cancelled" } ] } Push 2
  76. 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
  77. Authentication ➡ HMAC ➡ json + secret key + timestamp

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

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

    (UTC) ➡ Each push is only valid for: ➡ the exact body you intended ➡ a small time window
  80. 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' } })
  81. 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' } })
  82. 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' } })
  83. 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' } })
  84. 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' } })
  85. Push support for Spree ➡ Built-in to Spree 2.3 ➡

    A new ‘core’ gem ➡ Disabled by default, activated via preference.
  86. 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*
  87. 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
  88. Events “Tom completed an order for 3 t-shirts” “Tom’s order

    has been dispatched” “A new user called Jane registered”
  89. 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”
  90. 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”
  91. 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”
  92. 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”
  93. Events ➡ Created as objects change ➡ Two different ways

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

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

    they can be generated: ➡ created vs. updated - automatic ➡ event builder - custom ➡ Connect objects to webhooks*
  96. Events: Create Vs. Update ➡ The first time the hub

    receives an object: ➡ object:created ➡ order:created, inventory:created, pirate:created
  97. 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
  98. Webhooks - Goals ➡ A set of common patterns for

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

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

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

    processing events ➡ Well documented ➡ Easy to test ➡ Make integration development even easier
  102. Webhooks - Objects Standard naming convention for webhooks: ➡ add

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

    - alerts target system of new object ➡ update - sends object changes ➡ get - fetch object changes
  104. Webhooks - Objects ➡ add_order ➡ update_order ➡ get_orders Order

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

    ➡ add_product ➡ update_product ➡ get_products Product ➡ add_customer ➡ update_customer ➡ get_customers Customer
  106. 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
  107. Webhooks - Other ➡ get_inventory ➡ set_inventory Inventory ➡ send_email

    ➡ send_sms ➡ create_ticket ➡ add_to_list Comms.
  108. Implementing Webhooks ➡ An application capable of receiving a HTTP

    POST request. ➡ Can use any language or framework.
  109. 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.
  110. 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.
  111. 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.
  112. /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
  113. /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
  114. /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
  115. /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", }
  116. /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", }
  117. /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", }
  118. /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
  119. /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
  120. /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", }
  121. /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", }
  122. /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", }
  123. Webhook: Parameters { "request_id": "52f367367575e449c3000001", "order": {}, "parameters": { "quickbooks.access_token":

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

    "qyprd1vQF...", "quickbooks.access_secret": "xTrT6dckbY...", "quickbooks.realm": "1231232" } } POST
  125. 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", }
  126. /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" } }
  127. /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" } }
  128. /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" } }
  129. /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
  130. /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", }
  131. /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", }
  132. /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", }
  133. Authentication ➡ With each webhook, the hub includes the following

    headers: ➡ X-Hub-Store ➡ Unique store identifier
  134. 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.
  135. endpoint_base ➡ Supports both Sinatra & Rails endpoints, & provides:

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

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

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

    ➡ Authentication ➡ Helper methods & variables ➡ @payload & @config ➡ Response DSL ➡ Rendering
  139. /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
  140. /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.", }
  141. 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
  142. Retry Patterns: Back-off hub /add_order 500: fail! /add_order 200: success

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

    PUSH 200: success 500: fail! /add_order 500: fail! /add_order *
  144. Macros ➡ Embedded javascript functions ➡ Get applied to the

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

    body of a webhook ➡ before it’s sent to the integration ➡ Has access to:
  146. 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).
  147. 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.
  148. 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
  149. 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
  150. 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
  151. 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
  152. 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 += '<tr><td>' + item.name + '</td>\ <td>' + item.quantity + '</td>\ <td>$' + Number(item.price).toFixed(2) + '</td></tr>'; }); request.email = email;
  153. 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 += '<tr><td>' + item.name + '</td>\ <td>' + item.quantity + '</td>\ <td>$' + Number(item.price).toFixed(2) + '</td></tr>'; }); request.email = email;
  154. 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 += '<tr><td>' + item.name + '</td>\ <td>' + item.quantity + '</td>\ <td>$' + Number(item.price).toFixed(2) + '</td></tr>'; }); request.email = email;
  155. 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 += '<tr><td>' + item.name + '</td>\ <td>' + item.quantity + '</td>\ <td>$' + Number(item.price).toFixed(2) + '</td></tr>'; }); request.email = email;
  156. 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 += '<tr><td>' + item.name + '</td>\ <td>' + item.quantity + '</td>\ <td>$' + Number(item.price).toFixed(2) + '</td></tr>'; }); request.email = email;
  157. 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 += '<tr><td>' + item.name + '</td>\ <td>' + item.quantity + '</td>\ <td>$' + Number(item.price).toFixed(2) + '</td></tr>'; }); request.email = email;