Start a journey from an orderMenu
On this page

Start a journey from an order

Updated

Track Order Completed for a contact and send them a thank-you email from a journey that the order starts.

This recipe sends a thank-you email after every order. You create a journey that starts on the Order Completed event, activate it, track an order for a contact, and check that the contact entered the journey.

Every POST here carries an Idempotency-Key, so running a step twice is safe: the second run returns the first response. If you change a request’s body, change its key too, or the API returns 409 IDEMPOTENCY_KEY_REUSED.

Before you start

  • An API key with journeys:write, journeys:activate, contacts:write, events:write, contacts:read and messages:read. See Authentication.
  • Room in the plan’s event allowance. Free takes 1,000 events a month, enough to try this recipe; past the allowance, tracking returns 403 PLAN_LIMIT_REACHED.
  • What marketing email needs: a from address on a verified sending domain with a DMARC record, and the workspace’s postal address. Activation checks both.
  • cURL and jq, and KIRORA_API_URL and KIRORA_API_KEY set as in the Quickstart.

Replace mail.juniperkitchen.com with your verified domain in step 1.

1 Create the journey

The journey starts on Order Completed (names are case-sensitive), lets a contact enter again with every order, and sends one email that names the order from the event’s properties:

Terminal
curl "$KIRORA_API_URL/v1/journeys" \
  --fail-with-body --silent --show-error -o journey.json \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: recipe-order-journey-create-001" \
  -d '{
    "name": "Order thank-you",
    "trigger": {
      "type": "event",
      "name": "Order Completed",
      "reentry": { "mode": "repeatable", "waitAfterEntry": { "value": 0, "unit": "days" } }
    },
    "steps": [
      {
        "id": "thank-you-email",
        "type": "send",
        "channel": "email",
        "content": {
          "subject": "Thanks for your order",
          "from": "Juniper Kitchen <[email protected]>",
          "templateSnapshot": {
            "schemaVersion": 1,
            "blocks": [
              { "id": "title", "type": "heading", "text": "Thanks for your order" },
              { "id": "body", "type": "text", "text": "We have order {{ event.properties.order_id | default: \"your order\" }} and are packing it now." },
              { "id": "footer", "type": "footer", "text": "{{ org.name }}\n{{ org.address }}", "locked": true }
            ],
            "theme": {}
          }
        },
        "nextStep": null
      }
    ]
  }'
JOURNEY_ID=$(jq -er '.data.id' journey.json)

The response is 201 with the journey in status: "draft".

2 Activate it

Terminal
curl "$KIRORA_API_URL/v1/journeys/$JOURNEY_ID/activate" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: recipe-order-journey-activate-001" \
  -d '{}'

The response is 200 with the journey active. Only events tracked from now on start it. A 409 DRAFT_INCOMPLETE lists what is missing in errors; the other refusals are in Campaigns and journeys.

3 Create the customer

Marketing email goes only to contacts who consented. This creates the contact with your own id and records how they agreed. [email protected] is a test address: the email goes through every check, is reported as delivered, and reaches no inbox.

Terminal
curl "$KIRORA_API_URL/v1/contacts/identify" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: recipe-order-journey-contact-001" \
  -d '{
    "externalId": "customer-4821",
    "email": "[email protected]",
    "firstName": "Amaka",
    "consent": {
      "email": true,
      "statement": { "basis": "opted_in", "evidence": "Checkout opt-in on juniperkitchen.com" }
    }
  }'

The response is 201 when the contact is new, 200 when it already existed.

4 Track the order

Track Order Completed with the order’s properties, in major units (see E-commerce events). Use the order id in the Idempotency-Key, so a retried order is recorded once:

Terminal
curl "$KIRORA_API_URL/v1/events/track" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1001-completed" \
  -d '{
    "externalId": "customer-4821",
    "name": "Order Completed",
    "properties": {
      "order_id": "1001",
      "total": 45.00,
      "currency": "USD",
      "products": [
        { "product_id": "sku-paprika", "name": "Smoked paprika", "price": 15.00, "quantity": 3 }
      ]
    }
  }'

The response is 201: the event is stored. The journey picks it up in the background afterwards, not within this request. A contact that does not exist returns 404; tracking never creates one.

5 Check the enrollment

List the contact’s journeys, by your own id:

Terminal
curl "$KIRORA_API_URL/v1/contacts/customer-4821/journeys?idType=externalId" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY"

An entry whose journey.id is your journey’s shows the contact entered: status is active while it moves through the steps and completed once it has. If the list is empty, read it again shortly. Then read the message it sent:

Terminal
curl "$KIRORA_API_URL/v1/messages?journeyId=$JOURNEY_ID" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY"

For the test address the message ends delivered with simulated: true. To learn of each enrollment as it happens, subscribe to the journey.enrolled webhook instead of reading the list.

If nothing happens

  • No enrollment: the event name differs from the trigger’s name (case included), or the journey was not active when the event was tracked.
  • An enrollment, but the message is skipped: skip.reason says why, for example consent_missing when the contact has no email consent.

Next steps