Building a Forge worklog integration that holds up under load
A Forge app that syncs Jira worklogs with an outside time system works well at scale only if you design the experience around Jira roles, break API calls into small requests, and map user IDs between systems before you build. What keeps it working after launch is a staging environment, an automated pipeline, alerts, and documentation the customer's own team can use.
What the customer asked for
The customer wanted a Forge app that connected Jira to their time and billing system, the system that tracks what they invoice their own customers. Each user should be able to log time in that system and have it sync with the matching Jira work item, and worklogs in Jira should flow back the other way.
We have built this kind of worklog integration many times. It is one of the most common custom apps we see. The request usually sounds simple: move time data from one place to another. The hard parts sit around the data, not in it.
What they had not thought through
The customer was not surprised that the app would take real work. What they had not pictured was how it would feel to use, how it would perform with a lot of data, and who would keep it running.
Different people need different views
The customer knew what data they wanted users to enter. They had not thought about how the app should look to different people. An app admin, a space admin, and a person logging time each need different things. Showing every setting to everyone makes the app confusing and puts configuration in front of people who should not touch it.
Load from reporting
This was a large organization billing time against many of its own customers. The reporting side of the app had to pull a lot of worklog data, and loading all of it at once was slow. A report that makes people wait is a report they stop opening.
Matching people across systems
A worklog only means something if it belongs to the right person. User IDs in the billing system did not match Jira accounts on their own, so we had to map them. Without that mapping, time lands on the wrong person or nowhere at all.
Tokens that expired
The first connection to the billing system used a bearer token. Those tokens expired from time to time, and each time the sync stopped until someone replaced it. That is a maintenance task nobody remembers until it breaks.
Who supports it when it breaks
The customer wanted to deploy straight to production, and earlier releases had shipped with defects. There was no clear answer to who would maintain the app, how deployments worked, or how a new team member would learn it.
How we designed around it
We caught most of this during design, not after launch. Careful planning around expected load did most of the work.
- Design by role. We mapped what an app admin, a space admin, and a regular user each need, and hid configuration based on permissions so each person only sees what applies to them.
- Wireframe first. We showed the customer what each screen would look like before building it, and picked the layout with the least friction for the person logging time.
- Break up API calls. We segmented requests so the app asked for small, specific slices of data instead of pulling everything at once. That kept reports fast and stayed within platform limits.
- Map user IDs. We aligned accounts in the billing system with Jira accounts so every worklog tied to the right person.
- Roll up entries by space. Synced entries are recorded on each work item, and the space view adds them up. That gives a useful summary and makes it easy to confirm the sync is working.
- Add alerts. The app flags when something fails or needs maintenance, so the team hears about it before users do.
- Move to OAuth 2.0. We replaced the expiring bearer token with OAuth 2.0 so nobody has to rotate a key by hand. That took a workaround: we built a Forge web trigger inside the app to receive the authorization response.
How we made it maintainable
Design solved the load problem. Process solved the support problem.
- A staging environment. We set up a sandbox version of the app connected to a sandbox Jira site. Every new feature or upgrade gets tested there before it reaches production.
- A branch-based pipeline. We built the pipeline in Bitbucket Pipelines, with each branch mapped to an environment, so anyone can tell what is deployed where.
- Automated deployments. Once a change passes in staging, the pipeline promotes it to the next environment without manual steps.
- Checks before deploy. The pipeline flags problems before a deployment starts, which helped the customer's team while they were learning the app.
- Full documentation. We documented how the app works, how to maintain it, and how to make updates.
- A clear file structure. A predictable layout made the code easy for a new person to find their way around.
The decision they had to make
The biggest shift was about speed. At first, a staging environment and a standard pipeline looked like extra time. The customer was used to pushing changes straight to production.
We had to show them that moving more carefully at the start made everything after it faster. With proper testing and automated deployments, new features shipped sooner and with fewer defects. Alerts told them when something needed attention. Documentation meant their own team could understand every part of the app instead of waiting on us.
How it turned out
The customer's team now maintains the app themselves. They test in staging, deploy through the pipeline, and respond to alerts. Nothing about the app is hidden or held in one person's head. It is transparent, and that is what makes it supportable.
What I tell teams building on Forge
- Design around Jira roles, and hide settings from people who don't need them.
- Wireframe the experience before you build.
- Plan for your largest customer's data volume from the start.
- Request data in small pieces instead of all at once.
- Map user identities between systems before you sync anything.
- Avoid credentials that expire and need manual rotation. Use OAuth where you can.
- Never deploy straight to production. Test in a sandbox app and a sandbox site.
- Automate deployments and document the app so someone else can own it.
Where this connects to identity and agents
An integration like this is a machine identity. It reads and writes worklogs on behalf of real people, it holds credentials to an outside system, and it has to know which person each record belongs to. The same things that made this app safe to run are what agents need. That means credentials that do not quietly expire or get shared, a clear mapping between the system's actions and real users, settings that only the right roles can change, alerts when something goes wrong, and a tested path to production. An agent without those is a script nobody can support. An agent with them is something a team can trust and own.
Questions we get
Can a Forge app sync Jira worklogs with an outside time and billing system?
How should a Forge app handle different Jira roles?
Why is our Forge app slow with large amounts of data?
How do we match users between Jira and another system?
Should our integration use a bearer token or OAuth 2.0?
How do we make a custom app our own team can maintain?
Read next.
Custom development
Forge apps in production at scale, built by the architects who design the permission model underneath them.
Agent usage policy and guardrails
Usage policy and guardrails for Rovo, Forge, and MCP-connected agents: ownership, scope, cost governance, off switches, and audit trails.
Managed support and staffing
Run-layer support with defined SLAs, plus embedded architects and admins.
Bitbucket Pipelines
What Atlas Bench does with Bitbucket Pipelines: migration from external build tooling and connecting deployments to change control.
Forge
What Atlas Bench does with Atlassian Forge: custom applications and agents built inside the permission model you already govern.
Jira
What Atlas Bench does with Jira: migration, standardization of workflows and fields, hierarchy design, and the permission model underneath.