Webhooks

Sticky delivers webhooks to HTTPS URLs whenever an event is fired. To configure a webhook, head to "My account" in your dashboard, choose the "Advanced tab" and find the "Events webhook" box:

Webhooks must accept one of HTTP GET or HTTP POST methods and the request will be delivered with Content-Type application/json.

You will see a warning if the webhook URL does not start with https:// and the webhook will not be delivered:

Webhooks fail silently and there is no retry mechanism.

Do not use services like RequestBin to test webhooks. Webhook payloads will almost certainly contain sensitive data like PII, payment provider credentials or sticky locations. The same data available via our API with your private key will be sent in the webhook payload.

What to expect

Sticky will deliver a webhook for every event type. The event type is available under the event.type key, along with a unique ID (UUID) for the event:

"event": { "type": "SESSION_CART_PAY", "id": "---" }

The most common event types are:

  • SESSION_READ: Sticky tap
  • SESSION_CART_PAY: Payment
  • SESSION_CART_REFUND: Payment refund
  • CHECK_IN: Check in

The following keys are sent along with the event key. You must treat these keys as optional; for example a CHECK_IN event will not have a payment key:

  • sessionId: ID assigned to the consumer between taps
  • thing: Sticky object
  • application: Flow object
  • payment: Payment object
  • partnerId: ID of the user's partner, if they have one
  • federatedUserId: ID of team member, if there is one

Do not use sessionId as a guaranteed way to identify a consumer for all of time. The value will change if the consumer clears their mobile browser cookies or buys a new phone.

The whole payload:

{ "event": { "type": "SESSION_CART_PAY", "id": "---" }, "sessionId": "---", "thing": { "id": "---", "designId": "v2-order-here-food", "isKidSafe": false, "name": "Sticker 1", "location": { "latitude": 53.70258936, "longitude": -1.7806426 }, "customData": "{}" }, "application": { "id": "---", "name": "Show a list of products", "publicName": "[Sticky] James", "customData": "{}" }, "payment": { "id": "---", "sessionPaidAt": 1620820829, "cart": "[{"productId":"---","productPrice":500,"productCurrency":"GBP","quantity":1,"questions":[]}]", "type": "sticky", "refundedAt": null, "refundedTotal": 0, "currency": "GBP", "total": 500, "discount": 0, "tip": 0, "status": "new", "gateway": "GATEWAY_NOOP", "name": "Jack", "companyName": null, "email": "jack@gmail.com", "phone": null, "address": null, "giftAid": false, "marketing": false, "extra": null, "userPrivateKey": null, "paymentGatewayId": "---" } }
  • thing.id: Peristent ID for the sticky that was tapped
  • thing.isKidSafe: If true you have marked this sticky as safe for kids, so the sessionId key will always be the same
  • application.id: Peristent ID for the flow connected to the sticky that was tapped
  • payment.id: Peristent ID for the payment
  • payment.sessionPaidAt: Time the consumer paid in Unix time
  • payment.type:
    • sticky: Paying/paid with a sticky
    • external: Paying/paid not on a sticky (e.g. third party POS)
  • payment.total: Total charged by the payment provider to the consumer as an integer (the amount the consumer was actually charged, e.g. 500 for £5.00)
  • payment.status (this is not an exhaustive list):
    • new: Not paid yet (important: the webhook may deliver before this changes to paid)
    • paid: Payment succeeded
    • failed: Payment failed
    • done: Marked as served anywhere in the dashboard
  • payment.gateway: Payment provider (this is not an exhaustive list):
    • (null or undefined): Not connected
    • GATEWAY_NOOP: Stickypay in demo mode (consumer is not charged)
    • GATEWAY_STICKYPAY: Stickypay
    • GATEWAY_STRIPE: Stripe
    • GATEWAY_CARD: Card (external)
    • GATEWAY_CASH: Cash (external)
  • payment.paymentGatewayId: ID from the payment provider (pi_xxx if Stripe)

Worked example: Third party remittance

You are a third party platform that runs charity remittance from a Stripe account. You need:

  • To make sure the event type is SESSION_CART_PAY
  • To make sure the payload has a payment object
  • To make sure the payment was from Stripe (e.g. not a test payment with GATEWAY_NOOP Stickypay demo payment provider)
  • To make sure the payment was successful (payment.sessionPaidAt is a number)
  • The consumer's email address (payment.email)
  • Whether the consumer has consented to marketing (payment.marketing is true)
  • Whether the consumer is eligible for UK gift aid (payment.giftAid is true or false)

if (body.event.type !== 'SESSION_CART_PAY') { console.log('not a payment event') return } if (!body.payment) { console.log('no payment') return } if (body.payment.gateway !== 'GATEWAY_STRIPE') { console.log('not a stripe payment') return } if (typeof body.payment.sessionPaidAt !== 'number') { console.log('payment was not successful') return } if (body.payment.marketing && body.payment.email) { signUpForMailingList(payment.email) } scheduleRemittance(body.payment.paymentGatewayId, body.payment.total) // pi_xxx / 500

Debugging over HTTPS

Webhooks must be delivered over HTTPS. We recommend ngrok for testing webhook delivery before going live.

Our developers use an ngrok config file (ngrok.yml) to configure webhooks like this:

authtoken: --- region: eu tunnels: myservice: proto: http addr: 8001 subdomain: myservice

Be sure to set the authtoken line. A service on port 8001 will then receive the webhook through the address https://myservice.eu.ngrok.io. A basic example of a node.js service for receiving webhooks is below. Be sure to npm install restify. Note the server.post line which means the webhook must be of type HTTP POST.

const restify = require('restify') const server = restify.createServer() server.use(restify.plugins.queryParser()) server.use(restify.plugins.bodyParser({mapParams: false})) server.post( '/*', (req, res, next) => { console.log(req.body) res.send('ok') next() } ) server.listen(8001, () => console.log('%s listening at %s', server.name, server.url))