unPLUGDevelopers

Quickstart

Create a member in the sandbox, credit a paid order to them, and see the points land on their balance and in your webhook endpoint. Every step is one curl command.

Available at launch

Before you start#

  • A sandbox test key. Email support@unplugdining.com and ask for a sandbox. You get a uk_test_ key for your own copy of the demo environment. See Sandbox for what to include.
  • An HTTPS URL that shows incoming requests. Any request inspector works for this walkthrough. Later you'll point webhooks at your own server.
  • curl, and a way to make UUIDs (uuidgen on macOS and most Linux).

Put the key and host in your shell so the commands below run as written:

Shell
export UNPLUG_API=https://sandbox.api.unplugdining.com/v1
export UNPLUG_KEY=uk_test_...   # the key from support

Warning Keep the secret key on your machine or your server. Never paste it into an app, a web page or a public repo, even a test key.

1. Register a webhook endpoint#

Do this first so you see every event the next steps cause. Use "*" to receive all event types while you explore.

POSTRequest
curl -X POST $UNPLUG_API/webhook_endpoints \
  -H "Authorization: Bearer $UNPLUG_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "https://hooks.example.dev/loyalty-sandbox",
    "event_types": ["*"],
    "description": "Quickstart"
  }'
Response201
{
  "id": "52a7e1c9-4f3b-4d8a-9e6c-1b0d7f3a5e82",
  "url": "https://hooks.example.dev/loyalty-sandbox",
  "event_types": ["*"],
  "description": "Quickstart",
  "status": "enabled",
  "secret": "whsec_MfKQ9r8GKYqrTJ4kL2vJ8xVp3h7c2NnA",
  "created_at": "2026-11-03T18:12:05Z"
}

The secret is shown once. Save it: you need it to verify signatures.

2. Create a member#

Enroll a guest with your own id for them in external_id. The Idempotency-Key header is required here. If the request times out, send it again with the same key and you get the same member back, not a second one.

POSTRequest
curl -X POST $UNPLUG_API/members \
  -H "Authorization: Bearer $UNPLUG_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9e2c4a71-3b8f-4d5e-a1c6-7f0b2d9e8a34" \
  -d '{
    "external_id": "usr_10482",
    "first_name": "Maya",
    "last_name": "Lopez",
    "email": "maya@example.com",
    "communication_preferences": {
      "email_marketing": true,
      "sms_marketing": false,
      "push_marketing": true
    }
  }'
Response201
{
  "id": "3f2b8c1e-6d0a-4e7b-9f3c-2a1d5e8b7c40",
  "external_id": "usr_10482",
  "status": "active",
  "first_name": "Maya",
  "last_name": "Lopez",
  "email": "maya@example.com",
  "phone": null,
  "phone_verified": false,
  "birthday": null,
  "points_balance": 0,
  "lifetime_points": 0,
  "tier": { "id": "2b9e4f7a-1c3d-4e6b-8a5f-9d0c2e7b4a13", "name": "Green" },
  "next_tier": { "id": "6f1a8c3d-9b2e-4d7a-b5c6-3e0f8a1d2b47", "name": "Gold", "points_needed": 2000 },
  "communication_preferences": { "email_marketing": true, "sms_marketing": false, "push_marketing": true },
  "metadata": {},
  "enrolled_at": "2026-11-03T18:14:37Z",
  "created_at": "2026-11-03T18:14:37Z",
  "updated_at": "2026-11-03T18:14:37Z"
}

Keep the id. In your real integration you store it next to your user record. A member.created event arrives at your endpoint.

3. Credit a paid order#

Your server sends the order once payment is captured. First look up a location id:

GETRequest
curl $UNPLUG_API/locations \
  -H "Authorization: Bearer $UNPLUG_KEY"
Response200
{
  "data": [
    {
      "id": "8d4e2a17-3b9c-4f5e-a6d1-7c0b9e2f4a53",
      "name": "South Congress",
      "address": { "line1": "1412 S Congress Ave", "line2": null, "city": "Austin", "region": "TX", "postal_code": "78704", "country": "US" },
      "time_zone": "America/Chicago",
      "pos": "square",
      "pos_location_id": "LQ7H2XK9M3RTB",
      "status": "live"
    },
    {
      "id": "1c7f9b3e-5a2d-4e8c-b9f0-6d3a2e1c8b74",
      "name": "Riverside",
      "address": { "line1": "2201 E Riverside Dr", "line2": "Suite 110", "city": "Austin", "region": "TX", "postal_code": "78741", "country": "US" },
      "time_zone": "America/Chicago",
      "pos": "square",
      "pos_location_id": "L5VDN8P2WZ4KC",
      "status": "live"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Now post a $12.50 pickup order at South Congress: an oat latte, a drip coffee and a butter croissant. Each pos_item_id is the Square catalog variation id from the sandbox menu.

POSTRequest
curl -X POST $UNPLUG_API/orders \
  -H "Authorization: Bearer $UNPLUG_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: c41f8e2a-6d3b-4a9c-8e57-1b0d4f6a2c93" \
  -d '{
    "external_order_id": "web-88213",
    "member_id": "3f2b8c1e-6d0a-4e7b-9f3c-2a1d5e8b7c40",
    "location_id": "8d4e2a17-3b9c-4f5e-a6d1-7c0b9e2f4a53",
    "channel": "web",
    "fulfillment": "pickup",
    "placed_at": "2026-11-03T18:20:02Z",
    "currency": "USD",
    "subtotal_cents": 1250,
    "discount_cents": 0,
    "tax_cents": 103,
    "tip_cents": 200,
    "total_cents": 1553,
    "items": [
      { "line_id": "1", "pos_item_id": "HV4MZ2TQ7LW3KXRB5P8NJ6DC", "name": "Oat latte", "quantity": 1, "unit_price_cents": 575 },
      { "line_id": "2", "pos_item_id": "3NZQ7XKJ2Y5PLVKCW6RQ4G2T", "name": "Drip coffee", "quantity": 1, "unit_price_cents": 325 },
      { "line_id": "3", "pos_item_id": "7PKD3RXN2WQ6ZLHM4TB5JV8C", "name": "Butter croissant", "quantity": 1, "unit_price_cents": 350 }
    ]
  }'
Response201
{
  "id": "0f6d4b2e-8a1c-4d9f-b3e7-5c2a8f0d6e13",
  "external_order_id": "web-88213",
  "status": "credited",
  "points_earned": 125,
  "bonus_points": 0,
  "redemptions": [],
  "member": {
    "points_balance": 125,
    "tier": { "id": "2b9e4f7a-1c3d-4e6b-8a5f-9d0c2e7b4a13", "name": "Green" }
  },
  "created_at": "2026-11-03T18:20:04Z"
}

The demo program earns 10 Beans per dollar on the subtotal after discounts, rounded down. $12.50 with no discount is 125 Beans. Tax and tip don't earn.

Note external_order_id is unique per brand forever. Sending web-88213 again with a new Idempotency-Key returns 409 order_already_exists, so an order can never earn twice.

4. See the points and the webhooks#

Read the member back:

GETRequest
curl $UNPLUG_API/members/3f2b8c1e-6d0a-4e7b-9f3c-2a1d5e8b7c40 \
  -H "Authorization: Bearer $UNPLUG_KEY"
Response200
{
  "id": "3f2b8c1e-6d0a-4e7b-9f3c-2a1d5e8b7c40",
  "external_id": "usr_10482",
  "status": "active",
  "first_name": "Maya",
  "last_name": "Lopez",
  "points_balance": 125,
  "lifetime_points": 125,
  "tier": { "id": "2b9e4f7a-1c3d-4e6b-8a5f-9d0c2e7b4a13", "name": "Green" },
  "next_tier": { "id": "6f1a8c3d-9b2e-4d7a-b5c6-3e0f8a1d2b47", "name": "Gold", "points_needed": 1875 },
  "updated_at": "2026-11-03T18:20:04Z"
}

The response above is trimmed to the fields that changed. Your request inspector now shows three deliveries: member.created, points.changed and order.credited. They can arrive in any order. Here is points.changed:

WebhookPOST https://hooks.example.dev/loyalty-sandbox
webhook-id: 3c1f7a92-5e4b-4d6c-8f0a-2b9e1d7c4a63
webhook-timestamp: 1793730004
webhook-signature: v1,K7mQ2pX9vR4tL8nB3wY6cF1hJ5dS0gA2eZ7uN4iO9kE=
content-type: application/json
{
  "id": "3c1f7a92-5e4b-4d6c-8f0a-2b9e1d7c4a63",
  "type": "points.changed",
  "created_at": "2026-11-03T18:20:04Z",
  "api_version": "v1",
  "livemode": false,
  "brand_id": "e5a1c7d9-2f4b-4c8e-a3d6-8b0f1e9c2a57",
  "data": {
    "object": {
      "id": "a71e4c2b-9d3f-4b8a-8e6c-0f2d5b7a3c19",
      "member_id": "3f2b8c1e-6d0a-4e7b-9f3c-2a1d5e8b7c40",
      "type": "earn",
      "points": 125,
      "balance_after": 125,
      "description": "Order web-88213",
      "order_id": "0f6d4b2e-8a1c-4d9f-b3e7-5c2a8f0d6e13",
      "reward_id": null,
      "adjustment_reason": null,
      "created_at": "2026-11-03T18:20:04Z"
    },
    "previous_attributes": {}
  }
}

Your endpoint must answer with any 2xx within 10 seconds. If it doesn't, we retry with backoff for 3 days.

Next steps#

To start over, POST /v1/test/reset deletes every member and order in your sandbox brand. See Test helpers.