BizInstant

Order from SAP by chatting on WhatsApp: an AI agent on Amazon Bedrock collects the order, and a UiPath robot places it in SAP.

Inspiration

Every morning, Nadia’s inbox greeted her with the same flood. Where’s my invoice? When will my order arrive? Can you send my statement? As the customer service lead at a growing distribution company, she and her small team answered each question by hand. They opened one system for orders, another for payments, a third for deliveries, and pieced together answers like a puzzle.

It was exhausting, and the cost was real. Replies came late. Sometimes the numbers didn’t match across systems, and a vendor named Karim once received the wrong delivery date. His frustrated call stayed with Nadia for weeks. They don’t trust us anymore, she thought. And honestly, why should they?

One evening, staring at yet another spreadsheet, she asked herself a question that changed everything: What if people could just ask, the way they’d ask a person?

That question became BizInstand.

The idea was simple. Instead of making stakeholders wait, give them a familiar chat window where they could type naturally, “Where is order 4521?” or “Send me my latest statement,” and get an accurate answer instantly, pulled from every system behind the scenes. No more hunting. No more guessing. No more wrong data.

The first week after launch, Nadia watched the dashboard in quiet disbelief. Questions that once took hours were answered in seconds. Her team, freed from repetitive lookups, started doing what they’d always wanted to do: solving real problems and building relationships.

A month later, Karim sent her a message. It wasn’t a complaint this time. “Got my delivery date in ten seconds. Thank you.”

Nadia smiled and pinned it above her desk.

The lesson: Big transformations often begin with a frustration you refuse to accept as normal. The problems everyone has learned to tolerate are usually the ones waiting for someone to ask, “What if it could be simpler?”

What it does

  1. The customer messages a WhatsApp business number.
  2. AWS End User Messaging Social publishes the event to Amazon SNS, which feeds an Amazon SQS queue.
  3. A Lambda function runs a LangGraph agent on Amazon Bedrock (Amazon Nova 2 Lite). The agent:
    • verifies the user ID against DynamoDB;
    • offers the stored name, email and address for confirmation instead of asking again;
    • collects products and quantities;
    • pauses for an explicit yes / no before anything is placed.
  4. On "yes", the Lambda calls the UiPath Orchestrator API, and the robot creates the sales order in SAP.
  5. When the robot finishes, it calls back the Lambda, which sends the receipt and total to the customer on WhatsApp.

Conversation state lives in four DynamoDB tables: users, the conversation → workflow mapping, LangGraph checkpoints and chat history.

How we built it

Infrastructure as code. Everything AWS-side is Terraform, split into modules: messaging, message queue, conversation database (one module used for all four tables), AI agent IAM and message processor. Dev and prod share the same modules and differ only in their tfvars. State lives in S3 with native locking.

Two independent pipelines. The repo has two halves, each with its own GitHub Actions workflow triggered by path:

Folder Pipeline Deploys
IaC/ iac.yml SNS, SQS, DynamoDB, IAM, KMS, Lambda configuration
App/ app.yml Lambda code and the Python dependency layer

Terraform owns the Lambda's configuration. The App pipeline owns its code and layers, and Terraform is told to ignore those (lifecycle.ignore_changes). That way, an infrastructure change never rolls back the app, and an app change never touches environment variables. Both pipelines log in to AWS through GitHub OIDC, so no AWS keys are stored in GitHub.

A layer that's only rebuilt when it must be. The layer holds LangGraph, langchain-aws, FastAPI and Mangum. The App pipeline fingerprints the inputs:

$$ f = \operatorname{sha256}\big(\texttt{requirements.txt} \,|\, \texttt{build_layer.py}\big) \,|\, \text{runtime} \,|\, \text{arch} $$

It reuses an existing layer version whose description matches $f$, so a code-only change deploys in about a minute.

Challenges we ran into

1. Encryption that silently dropped every message. The SNS topic was encrypted with the AWS-managed key alias/aws/sns. Nothing in the account reported an error, yet the topic received nothing. A topic created by hand worked. The difference: the AWS-managed key's policy can't be edited and doesn't allow social-messaging.amazonaws.com, so every publish was refused at the key. The fix was a customer-managed key whose policy grants that service kms:GenerateDataKey* and kms:Decrypt, limited by aws:SourceAccount and aws:SourceArn. There's now also a validation rule rejecting alias/aws/sns.

2. "Signature expired" while uploading the layer. Publishing the ~48 MB layer directly to Lambda kept failing. A signed AWS request is only valid for 5 minutes, and upload time grows with size $S$ over bandwidth $B$:

$$ t_{\text{upload}} = \frac{S}{B} \approx 6.5\ \text{min} \;>\; 5\ \text{min} $$

Uploading to S3 instead (multipart, each part signed separately) and publishing the layer from S3 removed the problem.

3. Queue timeouts and duplicate orders. AWS rejects an SQS trigger whose visibility timeout $V$ is shorter than the Lambda timeout $T$, and recommends $V \ge 6T$. With $T = 180\,\text{s}$:

$$ V = 6 \times 180 = 1080\ \text{s} $$

We also set the batch size to 1. With a batch of $n$ messages, one timeout makes SQS redeliver all $n$, so $n - 1$ already-handled customers would get a duplicate reply, or worse, a duplicate order.

4. IDs that don't move between accounts. Moving to a new AWS account broke WhatsApp replies with "Origination phone number id not found". The phone number ID is assigned by the account that links the number, so each account gives it a new one. Because the Lambda's send permission is scoped to that ID, it has to be updated in Terraform, not just in the Lambda console.

5. Building Linux libraries on Windows. The layer is built on Windows but runs on Amazon Linux. The build script asks pip for manylinux wheels for the exact Python version and architecture of the function, so native packages like pydantic-core work on Lambda.

6. Smaller surprises.

  • Test messages: plain-text messages typed into the SNS console aren't JSON, crashed the parser and ended up in the dead-letter queue. They're now logged and skipped.
  • Function URLs: since October 2025 a public Function URL needs two resource-policy statements, not one, and needs a newer Terraform AWS provider to express them.

Accomplishments that we're proud of

  • A working end-to-end chat. A real WhatsApp message travels through End User Messaging Social, SNS, SQS, Lambda and Bedrock, and the customer gets the agent's reply back on WhatsApp.
  • A safe order flow. The customer must be a known user, and nothing reaches the RPA robot until they answer an explicit "yes". An unclear reply re-asks instead of guessing.
  • Rebuildable from scratch. We deployed the whole stack into a second AWS account from the same code, and a single manual workflow run can destroy it again.
  • Secure by default. No AWS keys in GitHub (OIDC), an encrypted SNS topic with a scoped customer-managed key, least-privilege IAM for the Lambda, and the order API key kept out of the repository.
  • Fast, independent deploys. App changes ship in about a minute without touching infrastructure, and infrastructure changes never overwrite the app.

What we learned

  • Silent failures are the expensive ones. The KMS issue produced no error anywhere. Comparing a working and a broken resource side by side found it faster than reading logs.
  • Decide who owns what. Splitting configuration (Terraform) from code (App pipeline) is what made two independent workflows possible without them undoing each other.
  • Not everything is code. Linking WhatsApp to AWS needs Meta's sign-up and can't be automated in Terraform. Writing those manual steps down is part of the infrastructure.
  • Human confirmation belongs in AI flows. LangGraph's interrupt lets the agent pause until the customer says "yes" before an order reaches SAP.

What's next for BizInstant

  • Read from SAP, not just write: let customers ask for their order list or invoices and get them back in the chat.
  • Secure the RPA callback with a shared secret, so the Function URL can be turned on in production.
  • Move the order API key from Lambda environment variables to AWS Secrets Manager.
  • Richer messages: WhatsApp buttons for the yes/no confirmation, and support for images such as a photo of a shopping list.

Built With

Share this project:

Updates

Submission history