Private preview
Handle errors Private preview
Learn how to handle common errors during extension runs.
This guide covers how to handle errors when your script calls external endpoints using endpointFetch. Before you begin, make sure you’ve created an extension and set up an endpoint to invoke.
Note
Currently, Stripe only supports invoking endpoints from custom workflow actions. See Build a custom action with a script.
You don’t need to import endpointFetch because it’s a global function injected by the Stripe runtime. It’s available at runtime only, not in tests or local builds. It throws exceptions on non-2xx HTTP responses, network failures, or misconfigurations.
Handle errors in a script
You can handle recoverable HTTP errors directly in your script. In this example, the script catches a 404 response and falls back to creating the missing resource, while allowing non-recoverable errors such as the related setting and the related setting to propagate.
try {
const result = await endpointFetch({
endpoint: 'com.my_script.send_notifications',
path: '/api/notifications',
method: 'POST',
body: JSON.stringify({
message: `Payment received from ${customInput.name}`,
}),
});
const data = JSON.parse(result.body);
return { success: true, ts: data.ts };
} catch (e) {
if (e.status === 404) {
const result = await endpointFetch({
endpoint: 'com.my_script.send_notifications',
path: '/api/contacts',
method: 'POST',
body: JSON.stringify({ id: customInput.id, email: customInput.email }),
});
return { contact: JSON.parse(result.body), created: true };
}
throw e;
}
Success responses (2xx)
The endpoint returns a 2xx status code.
In your script, you get a result object:
const result = await endpointFetch({
endpoint: 'slack_api',
path: '/chat.postMessage',
method: 'POST',
body: JSON.stringify({ channel: '#billing-alerts', text: 'Invoice paid' }),
});
// result = { ok: true, status: 200, body: '{"ok":true,"channel":"C123","ts":"1234567890.123456"}' }
Typical handling:
const result = await endpointFetch({
endpoint: 'slack_api',
path: '/chat.postMessage',
method: 'POST',
body: JSON.stringify({ channel: '#billing-alerts', text: 'Invoice paid' }),
});
const data = JSON.parse(result.body);
return { posted: true, ts: data.ts };
Client errors (4xx)
The endpoint rejects the request with a 4xx status code, such as 400 Bad Request, 403 Forbidden, 404 Not Found, or 429 Rate Limited.
In your script, endpointFetch throws:
await endpointFetch({
endpoint: 'slack_api',
path: '/chat.postMessage',
method: 'POST',
body: JSON.stringify({ channel: '#nonexistent', text: 'hello' }),
});
// Throws: Error {
// message: "Endpoint returned HTTP 400",
// code: "EXT_BAD_REQUEST",
// status: 400,
// body: '{"ok":false,"error":"channel_not_found"}',
// stack: "..."
// }
The error includes:
- e. message : "Endpoint returned HTTP {status}"
- e. code : An EXT _ * code mapped from the HTTP status
- e. status : The numeric HTTP status code
- e. body : The response body string, or null if the endpoint returns an empty response
- e. stack : The standard V8 stack trace
Typical handling:
try {
const result = await endpointFetch({
endpoint: 'slack_api',
path: '/chat.postMessage',
method: 'POST',
body: JSON.stringify({ channel, text: message }),
});
const data = JSON.parse(result.body);
return { success: true, ts: data.ts };
} catch (e) {
if (e.status === 429) {
return { success: false, retriable: true, reason: 'rate_limited' };
}
if (e.status) {
const error = e.body ? JSON.parse(e.body) : {};
return { success: false, status: e.status, error: error.error };
}
throw e; // re-throw non-HTTP errors
}
Server errors (5xx)
The endpoint reports a server-side failure with a 5xx status code, such as HTTP 500, 502 Bad Gateway, or 503 Service Unavailable.
In your script, the error matches 4xx errors: an exception with code, status, body, and stack. body might be null if the endpoint returns an empty response.
await endpointFetch({
endpoint: 'slack_api',
path: '/chat.postMessage',
method: 'POST',
body: JSON.stringify({ channel: '#billing-alerts', text: 'Invoice paid' }),
});
// Throws: Error {
// message: "Endpoint returned HTTP 503",
// code: "EXT_RESOURCE_UNAVAILABLE",
// status: 503,
// body: null,
// stack: "..."
// }
Typical handling:
try {
const result = await endpointFetch({
endpoint: 'slack_api',
path: '/chat.postMessage',
method: 'POST',
body: JSON.stringify({ channel, text: message }),
});
return { success: true };
} catch (e) {
if (e.status >= 500) {
return { success: false, retriable: true, status: e.status };
}
throw e;
}
Network errors
Network errors occur when Stripe accepts the request, but the call to the endpoint fails. That includes:
- DNS resolution failure for the endpoint host
- Connection refused by the endpoint
- Endpoint request timeout
If you don’t catch the error, the script run fails with a runtime_error error code (non-retryable).
In your script:
await endpointFetch({
endpoint: 'slack_api',
path: '/chat.postMessage',
method: 'POST',
body: JSON.stringify({ channel: '#billing-alerts', text: 'Invoice paid' }),
});
// Throws: Error { message: "Network error", code: "ERR_NETWORK", stack: "..." }
Properties:
- e. message : Always "Network error"
- e. code : "ERR _ the related setting"
- e. stack : The standard V8 stack trace
Hostnames and low-level connection details aren’t exposed to scripts for security reasons.
Configuration errors
Configuration errors occur if Stripe can’t find a resource the script references. That includes:
- The endpoint name in the app manifest doesn’t exist
- The script ID isn’t found
Fix the manifest or deployment. If uncaught, the script run fails with a bad_request error code (non-retryable).
In your script:
await endpointFetch({
endpoint: 'nonexistent_api',
path: '/test',
method: 'GET',
});
// Throws: Error { message: "Endpoint not found: nonexistent_api", code: "ERR_CONFIG" }
The error has the following properties:
- e. message : Describes what’s missing (for example, "Script not found: scp _ 123" , "Endpoint not found: slack _ api" , or "Resource not found" when details aren’t available)
- e. code : "ERR _ the related setting"
There are no e.status or e.body properties. This isn’t an HTTP response.
In this case, let the error propagate. It’s a configuration or deployment issue, not something to handle at runtime in the script.
Error scenarios summary
Stripe doesn’t automatically retry failed endpointFetch calls. To retry, handle it in your script.
| Scenario | Exception? | e.code | e.status | e.body | Retryable? |
|---|---|---|---|---|---|
2xx success | No | N/A | N/A | N/A | N/A |
400 Bad Request | Yes | the related setting | 400 | Error body or null | No |
| 401 Unauthorized | Yes | the related setting | 401 | Error body or null | No |
403 Forbidden | Yes | the related setting | 403 | Error body or null | No |
404 Not Found | Yes | the related setting | 404 | Error body or null | No |
429 Rate Limited | Yes | the related setting | 429 | Error body or null | Yes |
| Other 4xx | Yes | the related setting | 4xx | Error body or null | No |
| HTTP 500 | Yes | the related setting | 500 | Error body or null | No |
503 Unavailable | Yes | the related setting | 503 | Error body or null | Yes |
504 Timeout | Yes | the related setting | 504 | Error body or null | Yes |
| Other 5xx | Yes | the related setting | 5xx | Error body or null | No |
| Network failure | Yes | the related setting | N/A | N/A | No |
| Configuration error | Yes | the related setting | N/A | N/A | No |
